代码审计思路
一、说明
你要问我审计过什么项目发现过什么漏洞,那还真没有。记得第一家公司的老板娘问我“你会代码审记吗”,我不懂该怎么回答。要说懂我不肯定代码审计的具体定义是什么;要说不懂我看懂了ecshop和公司的代码框架,在我认为中看懂代码和源代码审计没什么区别嘛。现在说的话,真要说区别我觉得和渗透测试是测试的子集一样,代码审计也是“看懂代码”的子集。
子集换意思,就是基本能力和基本思想是可以通用的,但因为注意的点更集中,所以通用方法可以简化、优化出专门的方法。具体到看懂代码和代码审计上,看懂代码讲究从整体上弄清数据流向和各模块功能,代码审计则可以只追究局部,比如只要我看到char strdst[9];strcpy(strdst,"0123456789"),我不用明白程序功能是什么就可以断定这里必然溢出。
二、代码审计的思路
2.1 思路来源
如开头所说我没搞懂“代码审计”和“看懂源代码”的区别,所以买了本书,是尹毅的《代码审计:企业级Web代码安全架构》(名字很牛逼书却很薄,我经常想少于300页的书质量都不太行的。。),具体内容基本忘了,只记得其中提到代码审计大概有两种思路:第一种是正向追踪数据流,第二种是逆向溯源数据流。
前不久看王炜的《揭秘家用路由器0day漏洞挖掘技术》,其中也说代码审计大概有正向追踪和逆向溯源两种方式,另外进一步指出对于fgets/read/recv等输入函数适合正向追踪,对于strcpy/system等操作函数适合逆向溯源。
最后再回头去看Marcus Pinto的《黑客攻防技术宝典:Web实战篇》,其在第19章中说查找源代码中的源洞分三步:第一步是确定接收用户输入的位置,第二步是查找漏洞签名,第三步是详细分析漏洞签名的上下文看有没有漏洞。所谓漏洞签名就是漏洞经常出现的特征代码,更简单点说就是漏洞经常伴随出现的函数,比如缓冲区溢出的strcpy,再比如命令注入的sehll_exec等。
2.2 思路总结
我看遍了所有理论,感觉说得都看有道理,但要我进行代码审计,还是完全不懂该怎么操作。最主要的问题就是正向追踪,从什么位置开始追?追到什么位置算结束?逆向溯源,从什么位置开始溯?溯到什么位置算结束?
直到我反复琢磨以上三本书的说法,发现获取用户输入的函数与漏洞签名函数互为起止点。代码审计思路总结为以下四个步骤:
1. 确定要审计的代码是什么语言(比如是java还是.net)、什么框架(比如是ssh还是其他)。
2. 确定该语言及框架,从get/post中获取用户输入的函数或形式(比如java的getParameter,php的$_GET和$_POST);确定该语言各类漏洞的签名函数(比如JAVA和SQL漏洞相关的的是execute、executeQuery、createStatement,和命令执行漏洞相关的是getRuntime和exec等等)。
3. 使用Source Insight打开要审计的项目。
4. 使用search project在整个项目中查找获取用户输入函数和漏洞签名函数,对获取用户输入的函数使用正向追踪数据流看用户能控制的数据有没有进入漏洞签名函数,对漏洞签名函数使用逆向溯源数据流看数据来源是不是源于获取用户输入的函数。
其实我们去看代码审计工具,第一步都是查找获取用户输入函数和漏洞签名函数,第二步则是或正向或逆向追踪一下函数的变量(追踪能力的强弱差别则是审计工具能力强弱的差别)。比如下边两图是VCG(VisualCodeGrepper)和Seay对dvwa的审计结果:
一般而言,正向追踪能含盖更多的数据流分支,但有可能大多数数据都不会进入漏洞签名函数所以效率可能会低一点;逆向溯源从漏洞签名函数出来发现漏洞的概率会大一点,但是容易漏掉一些没有漏洞签名的函数(比如后门密码等)。
2.3 各语言获取用户输入及漏洞签名函数汇总
以下表格整理自《黑客攻防技术宝典Web实战篇》(第二版),并不全只是做个参考,加强所谓漏洞签名函数的意思(很多其实是类但是类还是函数并不是重点)。
语言 | 获取用户输入 | 会话交互 | sql注入 | 系统命令注入 | 动态代码执行 | 路径遍历 | url重定向 |
Java |
getParameter getHeader等 |
getAttribute setAttribute等 |
executeQuery executeUpdate |
getRuntime exec |
readObject writeObject |
FileInputStream FileReader |
sendRedirect addHeader |
.Net |
Params QueryString |
Add |
SqlCommand SqlDataAdapter |
Process ProcessStartInfo |
Execute ExecuteGlobal |
File.Open FileStream |
Redirect Addheader |
PHP |
$_GET $_POST |
$_SESSION session_register |
mysql_query mssql_query |
exec shell_exec |
eval call_user_func |
fopen include等 |
http_redirect header |
Perl |
param cookie |
pm | selectall_arrayref |
system exec |
eval |
open sysopen |
redirect |
三、具体操作演示
下面以从以前帮妹子写的一个毕业设计中通过逆向溯源查找sql注入来进行演示上一点所说的思路具休操作是怎么个样子(说来妹子最终都没是自己的妹子实在是悲剧)。
第一步----使用的语言和框架----java、struts2。
第二步----获取用户输入函数和漏洞签名函数-----struts2获取用户输入是通过继承ActionSupport实现getter/setter方法来获取的,java中sql注入的漏洞签名函数是executeQuery、executeUpdate。
第三步----使用Source Insight打开要审计的项目----结果如下图所示
第四步----使用search project查找executeUpdate并溯源其变量是否源于用户输入----操作过程如下
列出来的那些就是使用executeUpdate函数的位置,点击红色链接按钮即可跳转过去,在审计中我们将逐个审计这些位置。
我们点击链接按钮进入第一处,往前看executeUpdate执行的sql语句,可以看到语句中对大多数变量使用了预编译形式(ps.setString)但对user.getLoginPwd()使用了拼接方式
我们要追踪user.getLoginPwd()的来源,看是不是来自于获取用户输入的函数。user是register函数的参数,我们要回溯哪里调用了register。在register上右键,点击“Jump To Caller”
如下图所示即进入调用位置,我们往前看到user.setLoginPwd(),将光标置于其loginPwd参数上,在下方的定义窗口可以看到loginPwd是本类定义的一个变量
双击下方定义窗口的中“loginPwd”主窗口即跳转到该位置,观察上下文loginPwd正是继承ActionSupport类后实现的getter方法(getLoginPwd)从前端表单中获取的
所以,sql漏洞签名函数中,拼接的loginPwd变量来源于,用户输入;所以该处存在sql注入。