rips

抱着我一定要更新博客的决心, 我开始了RIPS折腾笔记。

0x00 背景

PHP静态分析有个里程碑式的工具-RIPS,这个工具是最早Dahse博士提出来的, 现在早已闭源。

截屏2020-08-12 下午12.11.26

到0.55是开源的最后一个版本。

10年到17年期间,Dahse发表的文章有:

  • 2010 - RIPS-A static source code analyser for vulnerabilities in PHP scripts : 简单介绍RIPS工具,对应在0.3版本
  • 2014 - Simulation of Built-in PHP Features for Precise Static Code Analysis: 发表在NDSS上, 如何模拟内建函数来精确静态分析
  • 2014 - Static Detection of Second-Order Vulnerabilities in Web Applications: 发表在USENIX上,二次注入漏洞探测问题。
  • 2014 - Code Reuse Attacks in PHP: Automated POP Chain Generatio: 发表在CCS上,POP链探测
  • 2015 - Experience report: an empirical study of PHP security mechanism usage: 一个软工的B
  • 2016 - Thesis. Static Detection of Complex Vulnerabilities in Modern PHP Applications: 总结性的文章,毕业了...

不得不说, 时间点卡的非常好, 10-15年正是PHP发展火爆的上升期,在14年发表了三篇也是醉了。如今四大上关注PHP的文章寥寥。


0x01 0.32版本工作流程

能找的最早版本,是RIPS-0.32,从这一版开始,结合文章资料开始分析,看RIPS是如何发展起来的。(此时还没有相对复杂的前端)

截屏2020-08-12 下午3.32.16

目录相对也简单, 直接index.php入口,也没有正则搜索功能了(虽然我平时只用这个)。

截屏2020-08-12 下午3.38.13

画风可以说非常的清气,板正。

6a04b428gy1g0zyyluaozg203j036no9

看代码入口处:

截屏2020-08-12 下午3.42.20

导入config里是一些需要使用的数据;functions里是处理token,扫描和输出三个功能;classes是定义了几个需要使用的数据结构(以类的形式,其实更像是结构体)。

根据作者的污点分析论述,此版本RIPS有139个PVFs(潜在漏洞函数),首先第一步找到这些漏洞函数,这些PVFs标记在config/PVF.php中,如一个命令执行:

截屏2020-08-12 下午3.52.20

我们扫描这样一段代码:

截屏2020-08-14 下午3.28.27

看到没,这是一个命令执行,最后输出:

截屏2020-08-14 下午3.29.18

从效果上看,是一个完整有效的污点分析了噜, 大体来看这段代码的扫描是怎样实现的? 大致分为三个阶段:读代码->token化->扫描。

截屏2020-08-14 上午10.47.59

RIPS将所有文件读入到$data,然后循环针对每一个文件进行scan_file。

截屏2020-08-14 下午4.14.08

file_name文件名, scan_functions目标PVFs,其他T开头的都是从tokens中读取的标识好的token集合。

进入scan_file方法也就是进入了functions/scan.php这个文件中,它是扫描的主逻辑所在地,文件在此完成扫描。先进行token化代码然后在此之上进行一些处理

截屏2020-08-14 下午4.25.13

这里的$lines_pointer是一个scan_file里注册的一个局部变量,用来装目标源码的。总之做了一些处理,放入到$tokens这个就是待分析源吗的目标token,进行prepare_tokensfix_tokens,这里使用token进行污点分析的一个很关键的点就是我们分析每一个token的时候可以通过当前token的标号在$tokens这个数组中随意提取周围的token以确定代码环境。这样我们就需要把$tokens进行一些处理,比如去掉空格这种东西,因为$a=$b有的人会加任意空格$a = $b这样我们计算这个表达式的时候比如扫描到了$a我们需要知道这是不是一个declare就要在$tokens[i+1]找等号。这些工作是在token处理中完成的(prepare_tokens),然后这里是在一个基本块里的例子,fix_tokens没什么用所以先不理会。

接下来就是扫描阶段:

1
2
3
4
5
6
7
8
9
10
11
12
for ($i=0; $tokencount=count($tokens); $i<$tokencount; $i++) //遍历每一个token
{
$token = $tokens[$i]; //此处既有当前token又保留了tokens可以随意提取周围token。
if (is_array($token))
{
...token是数组处理
}
else
{
token不是数组处理
}
}

此处迭代每一个$tokens。分为数组和非数组两大块,每一块又分不同的情形:

截屏2020-08-14 下午4.51.44

这里简单记录了一下他的注释。针对每种情况的不同 RIPS有自己处理的case。

截屏2020-08-14 下午5.09.07

看对上边的代码进行扫描,起作用的token,看到这个最简单的污点追踪,分别有variable declarations和标红的check if token is a function call and a function to scan

变量声明信息

在扫描到第0个token的时候,进入这个判断:

1
2
3
4
5
6
7
8
9
10
11
12
else if( $token_name === T_VARIABLE
&& ( $tokens[$i+1][0] === '=' || // normal assignment
(in_array($tokens[$i+1][0], $T_ASSIGNMENT)) // mathematical assignment
|| ($tokens[$i-1][0] === T_AS // foreach($var as $key=>$value)
|| ($tokens[$i-1][0] === T_DOUBLE_ARROW
&& $tokens[$i-2][0] === T_VARIABLE))
|| ($tokens[$i+1] === '[' // $foo['a'], hard to check all keys and assignments
// example: $a[0][$i+$k] &= $_GET['a'];
// easier: the last token was an ending statement or beginning of the file
&& ($tokens[$i-1] === '}' || $tokens[$i-1] === '{'
|| $tokens[$i-1] === ';' || !isset($tokens[$i-1][0])))
)

这个条件很长, 为了涵盖所有变量声明的形式,此处是最简单的normal assignment也就是在$tokens[$i+1][0]处发现了=,即0号token是一个变量而1号token是一个'='也就是说这个变量是一个左值,他正在被注册,因此进入该逻辑,new 一个varDeclare来注册这个变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php	
// variable declarations = childs
class VarDeclare
{
public $id;
public $value;
public $line;
public $marker;
public $dependencies;

function __construct($value = null)
{
$this->id = 0;
$this->value = $value;
$this->line = '';
$this->marker = 0;
$this->dependencies = array();
}
}

首先进入这个逻辑:

截屏2020-08-14 下午5.27.13

先经过getmultiline的处理(提取整句语句),然后注册一个对象,填数据。

截屏2020-08-14 下午5.29.19

然后把它插入到$var_declares_global中供之后分析使用(如果是函数内部的变量注册到$var_declares_local数组中)。同理,第7个token亦是如此。

截屏2020-08-14 下午5.32.23

这样我们便能拿到所有的变量声明信息。

PVF分析

当到走到第四行的systemtoken后判断他是一个PVF。这是后就注册一个VlnTreeNode然后进行污点分析, 因为认为这个污点函数的参数是之前的变量传播的,与后边的token无关,因此触发一次污点分析。

找到PVF system的token后, rips就开始生成一个VulnTreeNode对象,叫做$new_find,来记录这个污点链。

截屏2020-08-12 下午4.34.39

初始化了name,和lines(第4行的system)。接下来进行污点追踪,我们来看scan_parameter这个函数的标头:

1
2
function scan_parameter($file_name, $mainparent, $parent, $var_name, $var_declares, $last_token_id, $var_declares_global, $function_params, $function_obj, $userinput, $F_SECURES, $return_scan=false)

开始进入的时候$mainparent$parent 都是由$new_find传来的:

1
$userinput = scan_parameter($file_name, $new_find, $new_find, $trace_par_var, $var_declares_global, $i+$c, null, array(), null, false, $scan_functions[$token_value][1]);
截屏2020-08-14 下午9.20.34

这么进来了,$trace_par_var是第一个参数也就是$b,追踪这一个变量,直到找到相关的declare,然后递归...

截屏2020-08-14 下午9.22.15

此时栈中有两个scan_parameter了, 第一个是$b,第二个是$a,此时$mainparent不变,而$parent更新, $var_name也是这一次递归的目标变量$a,继续...

截屏2020-08-14 下午9.26.29

看一下三次递归:

  • $last_token_id: 13->7->0

  • $var_name: \(b->\)a->$_GET['a']

其中还有一些变量的变化还没有仔细去追。总之走到userinput之后,scan_parameter出栈的时候会通过output.php进行记录,然后整个结果展示粗来。

截屏2020-08-12 下午4.41.00

okay~ 大致是这样。

0x02 总结

目前已经基本走通了一个基本块内的污点分析,做为一个完整的污点分析,你甭管在什么层面,用什么方法,只要能达到效果就成, 这里在token层面进行分析,判断条件写的也很复杂,主要就是想要通过对token环境的约束覆盖所有的情况来。那么接下来还要考虑控制语句(包括三元运算符),过程间分析,OOP特性,语言动态特性...,总之,祝你幸福。

P.S. 本来想写完全分析的,有点长,有空再来篇新的。

Unknown