在安全领域里,每个安全研究人员在研究的过程中,也同样的不断地探索着如何能够自动化的解决各个领域的安全问题。其中自动化代码审计就是安全自动化绕不过去的坎。
SAST是什么?
ChatGPT:SAST是Static Application Security Testing(静态应用程序安全测试)的缩写。它是一种用于检测软件应用程序中潜在安全漏洞和代码缺陷的自动化安全测试方法。
在过去的十几年里,静态代码分析工具经历了长期的发展与演变过程,下面我们就一起回顾一下(下面的每个时期主要代表的相对的发展期,并不是比较绝对的诞生前后):
上古时期 - 关键字匹配
这里我们拿PHP做个简单的例子。
虽然我们匹配到了这个简单的漏洞,但是很快发现,事情并没有那么简单。
也许你说你可以通过简单的关键字重新匹配到这个问题。
beval($
- 高覆盖性 – 宁错杀不放过
这类工具最经典的就是Seay,通过简单的关键字来匹配尽可能多的目标,之后使用者可以通过人工审计的方式进一步确认。
bevalb(
- 可用性 – 宁放过不错杀
这类工具最经典的是Rips免费版
bevalb(
$_
(GET|POST)
但问题显而易见,高覆盖性和高可用性是这种实现方法永远无法解决的硬伤,不但维护成本巨大,而且误报率和漏报率也是居高不下。所以被时代所淘汰也是历史的必然。
近代时期 - 基于AST的代码分析
在分享这种原理之前,我们首先可以复习一下编译原理。拿PHP代码举例子:
通过词法分析和语法分析,我们可以将任意一份代码转化为AST语法树。PHP常见的语义分析库可以参考:
当我们得到了一份AST语法树之后,我们就解决了前面提到的关键字匹配最大的问题,至少我们现在对于不同的代码,都有了统一的AST语法树。如何对AST语法树做分析也就成了这类工具最大的问题。
在理解如何分析AST语法树之前,我们首先要明白infomation flow、source、sink三个概念:
- source: 我们可以简单的称之为输入,也就是infomation flow的起点
- sink: 我们可以称之为输出,也就是infomation flow的终点
- infomation flow,则是指数据流动的过程。
把这个概念放在PHP代码审计过程中,Source就是指用户可控的输入,比如$_GET、$_POST等,而Sink就是指我们要找到的敏感函数,比如echo、eval,如果某一个Source到Sink存在一个完整的流,那么我们就可以认为存在一个可控的漏洞,这也就是基于infomation flow的代码审计原理。
在明白了基础原理的基础上,我举几个简单的例子:
$a = $_GET[‘a’];
eval
($a);
Assignment(Variable(
'$a'
),ArrayOffset(Variable(
'$_GET'
),
'a'
),
False
)
Eval
(Variable(
'$a'
))
2、参数Variable(‘$a’)
3、Assignment函数
4、ArrayOffset
5、判断左值是不是Variable(‘$_GET’)
6、漏洞存在
在上面的分析过程中,Sink就是eval函数,Source就是$_GET
,通过回溯Sink的来源,我们成功找到了一条流向Source的infomation flow,也就成功发现了这个漏洞。
在分析infomation flow的过程中,明确作用域是基础中的基础. 这也是分析infomation flow的关键,我们可以一起看看一段简单的代码:
Function
get
($p)
{
echo
$p;
return
“
echo
2333
”;
}
$a = get($_GET[‘a’]);
eval
($a);
在这段代码中,从主语法树的作用域跟到Get函数的作用域,如何控制这个作用域的变动,就是基于AST语法树分析的一大难点,当我们在代码中不可避免的使用递归来控制作用域时,在多层递归中的统一标准也就成了分析的基础核心问题。
$a = $_GET[
'a'
]
function
ee
($p)
{
eval
($p)
}
ee($a)
2、获取当前语句所在作用域
3、当前作用域为ee函数,并存在参数传递
4、ee被标记为新的敏感函数
(2) 多重调用链
var
obj = {
url
: location.hash.split(
'#'
)[
1
],
fruit
:
null
};
function
loc
(
)
{
return
this
.url;
}
obj.fruit = loc;
eval
(obj.fruit());
如果说,前面的两个问题是可以被解决的话,还有很多问题是没办法被解决的,这里举一个简单的例子。
$_GET[
'a'
] = htmlspecialchars($_GET[
'a'
]);
echo
$_GET[
'a'
];
$_GET['a']
时,已经满足了从Source到sink的infomation flow,已经被识别为漏洞。一个典型的误报就出现了。也就是说,分析AST更接近分析代码,换句话就是说基于AST的分析得到的流,更接近脑子里对代码执行里的流程,忽略了大多数的分支、跳转、循环这类影响执行过程顺序的条件,这也是基于AST的代码分析的普遍解决方案,当然,从结果论上很难辨别忽略带来的后果。而基于IR/CFG这类带有控制流的解决方案,则是另一种解决思路。
首先我们得知道什么是IR/CFG:
- IR:是一种类似于汇编语言的线性代码,其中各个指令按照顺序执行。其中现在主流的IR是三地址码(四元组)
- CFG: (Control flow graph)控制流图,在程序中最简单的控制流单位是一个基本块,在CFG中,每一个节点代表一个基本块,每一个边代表一个可控的控制转移,整个CFG代表了整个代码的的控制流程图。
一般来说,我们需要遍历IR来生成CFG,当然,你也可以用AST来生成CFG,毕竟AST是比较高的层级。
而基于CFG的代码分析思路优势在于,对于一份代码来说,你首先有了一份控制流图(或者说是执行顺序),然后才到漏洞挖掘这一步。比起基于AST的代码分析来说,你只需要专注于从Source到Sink的过程即可。
但其实无论是基于哪种底层,后续的分析流程与AST其实别无太大的差别,挑战的核心仍然维持在如何控制流,维持作用域,处理程序逻辑的分支过程,确认Source与Sink。但其实无论是基于哪种底层,后续的分析流程与AST其实别无太大的差别,挑战的核心仍然维持在如何控制流,维持作用域,处理程序逻辑的分支过程,确认Source与Sink。上文中提到的是静态分析当中比较常见的一种作用域数据流回溯分析的思路,其实正向的污点分析,亦或者后来被人提到比较多的指针分析,核心思路大同小异。
而代码分析的基础方面,既然存在基于AST的代码分析,又存在基于CFG的代码分析,自然也存在其他的种类。比如现在市场上主流的fortify,Checkmarx,Coverity包括最新的Rips都使用了自己构造的语言的某一个中间部分,比如fortify和Coverity就需要对源码编译的某一个中间语言进行分析,又比如源伞实现了多种语言生成统一的IR,Joern使用了基于AST生成的CPG图结构进行分析。
事实上,无论是基于某种基础结构的代码分析,技术手段本身只有适应场景的不同,对于技术选型这件事情本身来说更重要的是你想要构建一个什么样的代码分析工具。
基于QL概念的框架 - CodeQL
而在代码分析领域,Semmle QL是最早诞生的QL语言,他最早被应用于LGTM,并被用于Github内置的安全扫描为大众免费提供。紧接着,CodeQL也被开发出来,作为稳定的代码分析框架在github社区化。
- https://securitylab.github.com/tools/codeql
- https://semmle.com/codeql
那么什么是QL呢?QL又和代码分析有什么关系呢?
首先我们回顾一下基于AST、CFG这类代码分析最大的特点是什么?无论是基于哪种中间件建立的代码分析流程,都离不开3个概念,流、Source、Sink,这类代码分析的原理无论是正向还是逆向,都是通过在Source和Sink中寻找一条流。而这条流的建立围绕的是代码执行的流程,就好像编译器编译运行一样,程序总是流式运行的。这种分析的方式就是数据流分析(Data Flow)。
而QL就是把这个流的每一个环节具象化,把每个节点的操作具象成状态的变化,并且储存到数据库中。
这样一来,通过构造QL语言,我们就能找到满足条件的节点,并构造成流。下面我举一个简单的例子来说:
$a = $_GET[
'a'
];
$b = htmlspecialchars($a);
echo
$b;
echo
=>
$_GET
.is_filterxss
select
*
where
{
Source
: $_GET,
Sink : echo,
is_filterxss : False,
}
但其实说到底这只是一个理念,并不是结果,以CodeQL为例子,其构建的一套语法规则并不能算是一套门槛很低的东西,反而其黑盒的底层阻止了安全研究人员进一步研究和使用CodeQL。
基于工具化的框架 - Joern
Joern的底层原理是一套基于AST生成的通用CPG(Code Property Graph)图,在图的上层实现了一套基于OverflowDb的查询语言以供使用者可以在不需要知晓底层原理的基础上查询分析。
在Joern里,我用的比较多,也是比较常见的一个场景就是寻找某个方法的调用关系。在Joern shell当中,你可以用非常简单的方法获取某个函数的调用位置,已经调用了该函数的函数。
当然,Joern在某些方面是成也工具化败也工具化,cpg本身强调调用关系和引用关系,Joern shell后端引用scala易用性有余实用性不足,你几乎很难在Joern的上层做数据流分析层面的分析。
商业代码分析的软件包括Checkmarx、Fortify等等,再到后来的CodeQL说到底其实都是技术长期积累的技术壁垒,很多问题也不是学术上的什么难点攻破。
而近几年越来越多的相关东西也如雨后春笋冒了出来,开源社区比较火的Joern、tabby、tai-e,其实技术原理上的东西大同小异,说到底就是还没有足够好用的产品出来,大多代码分析的软件还停留在某个底层技术的应用上。
而代码分析工具本身的易用性和场景化遇到的问题在我看来问题更大,即便是商业化程度非常高的Checkmarx这种软件也没法非常简单直白的接入到devsecops流程当中,很多工具甚至都解决不了高误报率和扫描效率低的问题,更谈不上实用了。在我看来,一款实用的好的代码分析软件还有很长的路要走。
3、fortify
【版权说明】
本作品著作权归LoRexxar所有
未经作者同意,不得转载
原文始发于微信公众号(破壳平台):人与代码的桥梁 - 聊聊SAST
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论