数据流敏感的代码漏洞检测
当读者读完前置的内容,已经基本对 SyntaxFlow 的使用风格有了入门印象了,SyntaxFlow 实际上是一个非常具有深度的技术,我们将在本章节为大家讲解 SyntaxFlow 的高级用法和高级特性,以便帮助用户解决非常难的技术问题。
什么是数据流路径敏感分析?
在静态代码分析领域,我们经常遇到一系列复杂的概念,如指针敏感性(pointer sensitivity)、数据流敏感性(data flow sensitivity)和路径敏感性(path sensitivity)等。大多数相关文献充斥着晦涩难懂的数学公式和LaTeX算法,这些表述方式往往给人以高深莫测的印象。令人遗憾的是,这些所谓的"高级技术"在实际工程实践和漏洞挖掘中的应用效果往往难以体现。更有甚者,相关的实现代码常常被刻意或无意地隐藏在文献的角落,使得这些技术与实际应用之间产生了巨大的鸿沟。
过分依赖理论推导和数学模型,而忽视了实际应用的重要性。这种做法不仅使得相关知识难以被广大工程师所理解和应用,更是阻碍了整个领域的发展。我们需要的是更加实用、直观、易于理解的教学和研究方法。
定义
在程序分析领域中,数据流分析(Data Flow Analysis)是一种静态分析技术,用于收集程序中数据在执行过程中如何流动和变化的信息。我们需要理解几个核心概念:
- 数据流(Data Flow):描述数据在程序中的传播路径和变化过程
- 控制流(Control Flow):描述程序执行的可能路径
- 路径敏感性(Path Sensitivity):分析时是否考虑程序的条件分支
数据流分析通常涉及以下要素:
数据流方向:自顶向下和自底向上
在进行数据流方向讨论的时候,我们考虑如下代码,可以加速用户理解这两个概念:
<?php
$llink=trim($_GET['query']);
$query = "SELECT * FROM nav WHERE link='$llink'";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$navs = mysql_fetch_array($result);
我们下面的案例多数基于 这一段 PHP 代码进行讨论
数据流分析可以从两个方向进行,每种方向都有其特定的应用场景和优势:
1. 自顶向下(Top-Down)
特点:
- 从数据源开始追踪
- 适合发现数据污染范围
- 可以识别多个受影响的汇点
- 计算开销较大
- 污点分析(Taint Analysis)
- 变量影响范围分析
- 数据依赖分析
2. 自底向上(Bottom-Up)
特点:
- 从敏感操作点开始反向追踪
- 直接定位潜在问题
- 分析效率较高
- 适合特定漏洞检测
- 更快地定位潜在漏洞
- 减少不必要的路径分析
- 聚焦于特定安全问题
比较分析
特性 | 自顶向下 | 自底向上 |
---|---|---|
起点 | 数据源(Source) | 数据汇(Sink) |
分析范围 | 全面 | 针对性强 |
性能 | 较慢 | 较快 |
适用场景 | 全局数据流分析 | 特定漏洞检测 |
资源消耗 | 较高 | 较低 |
- 选择合适的分析方向要考虑具体的应用场景
- 两种方向可以结合使用以获得更好的分析效果
- 需要权衡分析精度和性能开销
形式化表述
1. 自顶向下分析的形式化定义
在自顶向下分析中,我们从源点开始追踪数据流向所有可能的汇点。形式化定义如下:
其中数据流传播函数可以定义为:
2. 自底向上分析的形式化定义
自底向上分析从汇点开始反向追踪到可能的源点。形式化定义如下:
反 向传播函数定义为:
3. 路径敏感性分析
在考虑路径敏感性时,我们需要引入路径条件:
- : 节点n生成的数据流信息
- : 节点n终止的数据流信息
- : 节点n的前驱节点集合
- : 节点n的后继节点集合
- : 反向分析中节点n生成的数据流信息
- : 反向分析中节点n终止的数据流信息
虽然形式化定义看起来复杂,但在实际实现中,我们通常会根据具体场景进行简化和优化。关键是要理解核心概念和传播规则。
开始实战:SyntaxFlow 数据流分析
1. 生成数据流
还是考虑上面的那一段代码:
<?php
$llink=mysqli_real_escape_string(trim($_GET['query']));
$query = "SELECT * FROM nav WHERE link='$llink'";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$navs = mysql_fetch_array($result);
保存为 code.php
之后,执行 yak ssa -l php -t . -p dataflow
命令进行编译,编译后我们就可以对 dataflow
这个程序名对应的程序进行测试并且,编写 SyntaxFlow 规则。在编译过程中会有日志输出,当你看到:
...
...
...
...
[INFO] 2024-09-26 13:43:54 [language_parser:72] parsed file: [code.php]
[INFO] 2024-09-26 13:43:54 [language_parser:77] program dataflow finish
[INFO] 2024-09-26 13:43:54 [ssacli:189] finished compiling..., results: 1
[INFO] 2024-09-26 13:43:54 [database_profile:26] SSA Database SaveIrCode Cost: 23.121462ms
[INFO] 2024-09-26 13:43:54 [database_profile:27] SSA Database SaveIndex Cost: 5.212542ms
[INFO] 2024-09-26 13:43:54 [database_profile:28] SSA Database SaveSourceCode Cost: 359.583µs
[INFO] 2024-09-26 13:43:54 [database_profile:29] SSA Database SaveType Cost: 4.127001ms
[INFO] 2024-09-26 13:43:54 [database_profile:30] SSA Database CacheToDatabase Cost: 23.195708ms
之类的内容输出的时候意味着编译完成了。
随后创建扫描规则文件内容:
_GET.* as $params;
$params --> * as $sink;
alert $sink;
把上述规则保存为: rule.sf
然后再命令行中执行 yak sf rule.sf -p dataflow
将会看到输出为:
[INFO] 2024-09-26 13:48:03 [ssacli:539] start to use SyntaxFlow rule: rule.sf
...
...
...
[INFO] 2024-09-26 13:48:03 [ssacli:688] syntax flow query result:
rule md5 hash: 468a9fc888219cc95c0771b76b4ee88a
rule preview: _GET.* as $params; $params --> * as $sink; alert $sink;
description: {"desc":"","lang":"","level":"","title":"","title_zh":"","type":""}
Result Vars:
$sink:
t190614: eq(Undefined-mysql_query(add(add...ET.query(valid)))), "'")), true)
code.php:4:5 - 4:67
t190631: Undefined-mysql_fetch_array(Unde...ned-_GET.query(valid)))), "'")))
code.php:5:13 - 5:39
看到上述数据,基本说明我们获取到了数据流的两个关键最终点,一个是 mysql_fetch_array
的调用结果,另一个是 mysql_query
执行结果和 true
的比较函数(这是由 or
运算来产生的,不要惊慌)。
我们在这里已经成功获取到了数据流最终终结的位置,在这里看他还是相当准确的,把上述结果整理起来就可以看到真正的结果所有数据流相关的结果:
在途中我们可以很 清楚的发现,最终的结果其实是 eq
和一个 $navs
变量。那么光得到这一步,我们还不能说可以过滤出想要的结果,那么如何在规则中编写过滤语句呢?
事实上,我们发现,mysqli_real_escape_string 这个函数存在的话,漏洞发生就会变的困难。
2. 编写过滤语句
我们经过上述操作可以得到数据流最终的点,但是需要检查过滤的步骤,这个时候应该怎么做呢?我们在 SyntaxFlow 中可以通过 <dataflow()>
这个功能来实现:
我们把这个复杂的数据流捋清的过程封装成了一个叫 <dataflow>
的 NativeCall。用户可以调用这个指令,把数据流的所有路径整理成在一起,一起进行检查,直到过滤出自己想要的数据。
如果用户对这个东西有点陌生,可以参考 “基础” 章节的内容。
NativeCall 是 SyntaxFlow 提供的一种机制,允许用户在规则中调用一些内置的函数,这些函数通常是一些复杂的操作,例如数据流分析、污点分析等。通过 NativeCall,用户可以更方便地实现一些高级的分析功能,而无需手动编写复杂的逻辑。
继续案例中 PHP 的程序规则:
_GET.* as $params;
$params --> * as $sink;
$sink<dataflow(<<<CODE
*?{opcode: call && <getCaller><name>?{have: mysqli_real_escape_string }} as $__next__
CODE)> as $filtered;
$sink - $filtered as $vuln;
alert $vuln;
这段审计代码看起来就复杂了不少,我们直接把这段代码执行一下: yak sf -p dataflow rule.sf
发现他无法输出原来的信息了,因为我们过滤了 mysqli_real_escape_string
这个函数。
我们基于之前的案例创建一个有漏洞的代码(删除一些函数):
<?php
$llink=trim($_GET['query']);
$query = "SELECT * FROM nav WHERE link='$llink'";
$result = mysql_query($query) or die('SQL语句有误:'.mysql_error());
$navs = mysql_fetch_array($result);
在上述代码中移除了 mysqli_real_escape_string 函数,发现重新执行规则将会检查出来,那就发生了什么?接下来我们将解释一下这段代码究竟怎么回事儿:
SyntaxFlow 检测 SQL 注入的规则解释
1. 输入数据收集 _GET.* as $params;
这行代码从 PHP 的全局 $_GET
数组中获取所有参数,并将这些参数存储到变量 $params
中。这是对用户输入数据的初始收集阶段。
2. 数据流追踪 $params --> * as $sink;
这条指令建立了数据流追踪路径:
- 从
$params
开始追踪数据流向 *
表示追踪所有可能的目标位置- 结果存储在
$sink
变量中,代表所有潜在的危险使用点
3. 过滤器定义和使用 $sink<dataflow(...)>...
$sink<dataflow(<<<CODE
*?{opcode: call && <getCaller><name>?{have: mysqli_real_escape_string }} as $__next__
CODE)> as $filtered;
这段代码定义了数据流分析的核心过滤逻辑:
- 检测所有函数调用(
opcode: call
) - 特别关注调用
mysqli_real_escape_string
的情况 - 将经过安全处理的数据点标记为
$filtered