抱着我一定要更新博客的决心, 我开始了RIPS折腾笔记。
0x00 背景
PHP静态分析有个里程碑式的工具-RIPS,这个工具是最早Dahse博士提出来的, 现在早已闭源。
到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是如何发展起来的。(此时还没有相对复杂的前端)
目录相对也简单, 直接index.php入口,也没有正则搜索功能了(虽然我平时只用这个)。
画风可以说非常的清气,板正。
看代码入口处:
导入config里是一些需要使用的数据;functions里是处理token,扫描和输出三个功能;classes是定义了几个需要使用的数据结构(以类的形式,其实更像是结构体)。
根据作者的污点分析论述,此版本RIPS有139个PVFs(潜在漏洞函数),首先第一步找到这些漏洞函数,这些PVFs标记在config/PVF.php
中,如一个命令执行:
我们扫描这样一段代码:
看到没,这是一个命令执行,最后输出:
从效果上看,是一个完整有效的污点分析了噜, 大体来看这段代码的扫描是怎样实现的? 大致分为三个阶段:读代码->token化->扫描。
RIPS将所有文件读入到$data,然后循环针对每一个文件进行scan_file。
file_name文件名, scan_functions目标PVFs,其他T开头的都是从tokens中读取的标识好的token集合。
进入scan_file
方法也就是进入了functions/scan.php
这个文件中,它是扫描的主逻辑所在地,文件在此完成扫描。先进行token化代码然后在此之上进行一些处理
这里的$lines_pointer
是一个scan_file
里注册的一个局部变量,用来装目标源码的。总之做了一些处理,放入到$tokens
这个就是待分析源吗的目标token,进行prepare_tokens
和fix_tokens
,这里使用token进行污点分析的一个很关键的点就是我们分析每一个token的时候可以通过当前token的标号在$tokens
这个数组中随意提取周围的token以确定代码环境。这样我们就需要把$tokens
进行一些处理,比如去掉空格这种东西,因为$a=$b
有的人会加任意空格$a = $b
这样我们计算这个表达式的时候比如扫描到了$a
我们需要知道这是不是一个declare
就要在$tokens[i+1]
找等号。这些工作是在token处理中完成的(prepare_tokens
),然后这里是在一个基本块里的例子,fix_tokens
没什么用所以先不理会。
接下来就是扫描阶段:
1 | for ($i=0; $tokencount=count($tokens); $i<$tokencount; $i++) //遍历每一个token |
此处迭代每一个$tokens
。分为数组和非数组两大块,每一块又分不同的情形:
这里简单记录了一下他的注释。针对每种情况的不同 RIPS有自己处理的case。
看对上边的代码进行扫描,起作用的token,看到这个最简单的污点追踪,分别有variable declarations
和标红的check if token is a function call and a function to scan
变量声明信息
在扫描到第0个token的时候,进入这个判断:
1 | else if( $token_name === T_VARIABLE |
这个条件很长, 为了涵盖所有变量声明的形式,此处是最简单的normal assignment
也就是在$tokens[$i+1][0]
处发现了=
,即0号token是一个变量而1号token是一个'='也就是说这个变量是一个左值,他正在被注册,因此进入该逻辑,new 一个varDeclare
来注册这个变量。
1 | <?php |
首先进入这个逻辑:
先经过getmultiline的处理(提取整句语句),然后注册一个对象,填数据。
然后把它插入到$var_declares_global
中供之后分析使用(如果是函数内部的变量注册到$var_declares_local
数组中)。同理,第7个token亦是如此。
这样我们便能拿到所有的变量声明信息。
PVF分析
当到走到第四行的system
token后判断他是一个PVF。这是后就注册一个VlnTreeNode
然后进行污点分析, 因为认为这个污点函数的参数是之前的变量传播的,与后边的token无关,因此触发一次污点分析。
找到PVF system的token后, rips就开始生成一个VulnTreeNode
对象,叫做$new_find
,来记录这个污点链。
初始化了name,和lines(第4行的system)。接下来进行污点追踪,我们来看scan_parameter这个函数的标头:
1 | 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]); |
这么进来了,$trace_par_var
是第一个参数也就是$b
,追踪这一个变量,直到找到相关的declare,然后递归...
此时栈中有两个scan_parameter
了, 第一个是$b
,第二个是$a
,此时$mainparent
不变,而$parent
更新, $var_name
也是这一次递归的目标变量$a
,继续...
看一下三次递归:
$last_token_id: 13->7->0
$var_name: \(b->\)a->$_GET['a']
其中还有一些变量的变化还没有仔细去追。总之走到userinput之后,scan_parameter
出栈的时候会通过output.php
进行记录,然后整个结果展示粗来。
okay~ 大致是这样。
0x02 总结
目前已经基本走通了一个基本块内的污点分析,做为一个完整的污点分析,你甭管在什么层面,用什么方法,只要能达到效果就成, 这里在token层面进行分析,判断条件写的也很复杂,主要就是想要通过对token环境的约束覆盖所有的情况来。那么接下来还要考虑控制语句(包括三元运算符),过程间分析,OOP特性,语言动态特性...,总之,祝你幸福。
P.S. 本来想写完全分析的,有点长,有空再来篇新的。