长文,需要的话可以收藏保存
过本地搭建 Damn Vulnerable Web Application (DVWA) 学习漏洞的利用和修复思路的时候,就感觉通过阅读 low、medium、high、impossible 的不同等级的存在漏洞缺陷的源代码,可以对漏洞的成因有代码层面的理解,也方便给修复建议的时候和研发进行代码层面的交流。
于是就想系统地学习和练习代码审计这块的知识,目前主要的学习资料就是**《代码审计 企业级Web代码安全架构》这本书,PHP语言比较容易上手,环境搭建简单,搞Web的基本都会一点,而且PHP这门语言像腾讯、百度等都广泛运用到了Web上,还是很值得研究的。学习PHP的时候,除了网上的教程,我主要参考了《PHP和MySQL Web开发》**这本书。
这里就把我学习的笔记整理成博客,感觉有输出才能更好地巩固学习效果。
PHP代码审计菜鸟笔记系列:
摘要:学习到这里,就可以开始搭建PHP代码审计的练习环境了,一种比较简单的方式就是在Windows下边安装好phpStudy,然后安装 Notepad++ 编辑器和 Seay源代码审计系统。
PHP代码审计菜鸟笔记(二):通用代码审计思路-本文
摘要:学到这一节,就是结合例子了解下几种常见的审计思路,还有就是学会了Seay代码审计工具的基本操作。
本文涉及的工具附件,下载地址如下:
https://gitee.com/sosly/blogattachment/tree/master/PHP代码审计菜鸟笔记/
代码审计工具的实现大都是基于代码审计的经验和审计思路,然后把能够自动化的工作尽可能自动化,辅助提高审计效率。
我看了下“Seay源代码审计系统”的默认正则匹配规则,如下:
下边也可以填写测试数据,在工具里验证。
配置文件里也能找到这些规则的文本,感兴趣的可以研究,默认规则文本整理如下:
1 \b(include|require)(_once){0,1}(\s{1,5}|\s{0,5}\().{0,60}\$(?!.*(this->))\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} 文件包含函数中存在变量,可能存在文件包含漏洞
2 \bpreg_replace\(\s{0,5}.*/[is]{0,2}e[is]{0,2}["']\s{0,5},(.*\$.*,|.*,.*\$) preg_replace的/e模式,且有可控变量,可能存在代码执行漏洞
3 \bphpinfo\s{0,5}\(\s{0,5}\) phpinfo()函数,可能存在敏感信息泄露漏洞
4 \bcall_user_func(_array){0,1}\(\s{0,5}\$\w{1,15}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} call_user_func函数参数包含变量,可能存在代码执行漏洞
5 \b(file_get_contents|fopen|readfile|fgets|fread|parse_ini_file|highlight_file|fgetss|show_source)\s{0,5}\(.{0,40}\$\w{1,15}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} 读取文件函数中存在变量,可能存在任意文件读取漏洞
6 \b(system|passthru|pcntl_exec|shell_exec|escapeshellcmd|exec)\s{0,10}\(.{0,40}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} 命令执行函数中存在变量,可能存在任意命令执行漏洞
7 \b(mb_){0,1}parse_str\s{0,10}\(.{0,40}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} parse_str函数中存在变量,可能存在变量覆盖漏洞
8 \${{0,1}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1}\s{0,4}=\s{0,4}.{0,20}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} 双$$符号可能存在变量覆盖漏洞
9 ["'](HTTP_CLIENT_IP|HTTP_X_FORWARDED_FOR|HTTP_REFERER)["'] 获取IP地址方式可伪造,HTTP_REFERER可伪造,常见引发SQL注入等漏洞
10 \b(unlink|copy|fwrite|file_put_contents|bzopen)\s{0,10}\(.{0,40}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} 文件操作函数中存在变量,可能存在任意文件读取/删除/修改/写入等漏洞
11 \b(extract)\s{0,5}\(.{0,30}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1}\s{0,5},{0,1}\s{0,5}(EXTR_OVERWRITE){0,1}\s{0,5}\) extract函数中存在变量,可能存在变量覆盖漏洞
12 \$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1}\s{0,5}\(\s{0,5}\$_(POST|GET|REQUEST|SERVER)\[.{1,20}\] 可能存在代码执行漏洞,或者此处是后门
13 ^(?!.*\baddslashes).{0,40}\b((raw){0,1}urldecode|stripslashes)\s{0,5}\(.{0,60}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} urldecode绕过GPC,stripslashes会取消GPC转义字符
14 `\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1}` ``反引号中包含变量,变量可控会导致命令执行漏洞
15 \barray_map\s{0,4}\(\s{0,4}.{0,20}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1}\s{0,4}.{0,20}, array_map参数包含变量,变量可控可能会导致代码执行漏洞
16 select\s{1,4}.{1,60}from.{1,50}\bwhere\s{1,3}.{1,50}=["\s\.]{0,10}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} SQL语句select中条件变量无单引号保护,可能存在SQL注入漏洞
17 delete\s{1,4}from.{1,20}\bwhere\s{1,3}.{1,30}=["\s\.]{0,10}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} SQL语句delete中条件变量无单引号保护,可能存在SQL注入漏洞
18 insert\s{1,5}into\s{1,5}.{1,60}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} SQL语句insert中插入变量无单引号保护,可能存在SQL注入漏洞
19 update\s{1,4}.{1,30}\s{1,3}set\s{1,5}.{1,60}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} SQL语句delete中条件变量无单引号保护,可能存在SQL注入漏洞
20 \b(eval|assert)\s{0,10}\(.{0,60}\$\w{1,20}((\[["']|\[)\${0,1}[\w\[\]"']{0,30}){0,1} eval或者assertc函数中存在变量,可能存在代码执行漏洞
21 \b(echo|print|print_r)\s{0,5}\({0,1}.{0,60}\$_(POST|GET|REQUEST|SERVER) echo等输出中存在可控变量,可能存在XSS漏洞
22 (\bheader\s{0,5}\(.{0,30}|window.location.href\s{0,5}=\s{0,5})\$_(POST|GET|REQUEST|SERVER) header函数或者js location有可控参数,存在任意跳转或http头污染漏洞
23 \bmove_uploaded_file\s{0,5}\( 存在文件上传,注意上传类型是否可控
1)根据敏感关键字回溯参数传递过程;
2)查找可控变量,正向追踪变量传递过程;
3)寻找敏感功能点,通读功能点代码;
4)直接通读全文代码
下边会结合具体的实例来练习。
例如:
1)通过select、insert 结合from和where 等关键字,判定是一条SQL语句,然后通过对字符串的识别,判断这个SQL语句里边的参数有没有拼接或者 单引号过滤。
2)HTTP头里边的HTTP_CLIENT、HTTP_X_FORWARDFOR 等获取到的IP地址 经常没有过滤就直接拼接到了SQL语句中,并且因为是在$_SERVER 变量中,不受GPC的影响,因此可以查找HTTP_CLIENT、HTTP_X_FORWARDFOR 关键字来快速寻找漏洞。
然后来了一个espcms 的审计实例,这里我安装了一个2014年的旧版本的espcms 跟着操作,也复现了这个例子:
易思ESPCMSV5.9.14.08.28安装包 下载:
官网链接:http://www.ecisp.cn/html/cn/download/espcmstool/541.html
备用链接
其实这个例子,也讲解了Seay源代码审计系统的常用方法。
1)cms安装与环境准备
我的测试环境是Win10 操作系统中,安装了phpStudy 服务器环境,然后安装了“易思ESPCMSV5.9.14.08.28”,安装过程中自己设置管理员密码。安装后可以访问后台:
登录后是这个样子
这样基本的审计环境就搭好了,顺便我也安装了 Seay源代码审计系统。
2)利用Seay源代码审计系统 审计一个功能点
可以看着一起操作,我步骤写的很细致。这个例子也是“Seay源代码审计系统”的入门教学。
先“新建项目”载入程序,然后自动审计,注意文件夹选择 安装后的Web目录下的那个espcms的文件夹
我这里是第28行,这个文件,说的是里边的变量$parentid 无单引号保护,可能存在注入
/adminsoft/control/citylist.php
双击,跳转到代码:
然后左边可以跟踪变量,选中$parentid就看到这个变量的在当前文件的传递过程:
1)$parentid = $this->fun->accept('parentid', 'R');
2)$sql = "select * from $db_table where parentid=$parentid";
接下来定位函数:
双击点进去
function accept($k, $var = 'R', $htmlcode = true, $rehtml = false) {
switch ($var) {
case 'G':
$var = &$_GET;
break;
case 'P':
$var = &$_POST;
break;
case 'C':
$var = &$_COOKIE;
break;
case 'R':
$var = &$_GET;
if (empty($var[$k])) {
$var = &$_POST;
}
break;
}
$putvalue = isset($var[$k]) ? $this->daddslashes($var[$k], 0) : NULL;
return $htmlcode ? ($rehtml ? $this->preg_htmldecode($putvalue) : $this->htmldecode($putvalue)) : $putvalue;
}
这个函数大致意思就是,从Web请求包中获取 Get提交或者Post提交的对应参数。然后经过daddslashes() 函数过滤,这个函数就是包装了addslashes()函数,
需要说明的是,书里特别指出了一点:
addslashes() 过滤了单引号、双引号、NULL字符、斜杠。
只要参数在拼接到 SQL语句前,除非有宽字节注入或其他特殊情况,只要使用了 addslashes() 这个函数进行过滤,就不能注入了。
说到这,难道参数 $parentid 分析到这里就凉凉了?不是的,因为这里有个
$sql = "select * from $db_table where parentid=$parentid";
这个参数并不需要单引号来闭合 ,所以直接构造可以注入的语句,
如果这个语句写成下边这种情况:
$sql = "select * from $db_table where parentid='$parentid'";
因为量变有了单引号,这就属于“只要参数在拼接到 SQL语句前,除非有宽字节注入或其他特殊情况,只要使用了 addslashes() 这个函数进行过滤,就不能注入了”所描述的情况了。
好,既然这里可以利用,继续分析调用过程。
我们再回到
/adminsoft/control/citylist.php
既然oncitylist() 这个函数有问题,然后这个函数在类 important 里,就看哪里实例化了这个例,目的是找到调用这个函数的地方。
看到 /adminsoft/index.php 这里有:
这里读一下代码,涉及到2个参数 分别是 archive、action,初学的时候可能有些懵逼,我们不妨在未登录状态下,先访问
看看情况,发现默认情况下,URL会自动跳转到:
http://127.0.0.1/espcms/adminsoft/index.php?archive=adminuser&action=login
这和代码里的
$archive = indexget('archive', 'R');
$archive = empty($archive) ? 'adminuser' : $archive;
$action = indexget('action', 'R');
$action = empty($action) ? 'login' : $action;
对应,就是访问 /espcms/adminsoft/index.php
,没有参数的话,默认$archive 就是 adminuser,对应调用的文件就是/adminsoft/control/adminuser.php
$action 如果没有收到传入的参数,默认就是login,意味着就会调用adminuser.php 文件中的important实例化后的onlogin() 函数.
那么问题来了,我们的目的是触发 /adminsoft/control/citylist.php
文件中的 oncitylist 函数,那这个URL里的参数该如何构造?
依葫芦画瓢 ?archive=citylist&action=citylist
注意,刚才分析了 oncitylist() 函数调用了 accept(‘parentid’, ‘R’) 函数,去接收一个Get或者Post过来的参数 parentid
所以利用的Payload 应该构造为:
?archive=citylist&action=citylist&parentid=1
注意parentid 这个参数就是注入点,parentid的值可以随意尝试,这里就先写1 。
可以写我们的利用语句,完整就是:
http://127.0.0.1/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=1
由于这个注入点,在/espcms/adminsoft/
目录下,是需要管理员登录后才可访问的,所以我们先登录,再来测试这个注入点。
3)审计结果的验证
http://127.0.0.1/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=1
加一个单引号:
直接报错了。
如果有黑盒测试经验的话,这个时候用salmap跑就比较方便。
但是咱们这次是白盒审计,SQL语句、代码、数据库啥都能看到,我们就练习下白盒审计的思路。这部分要是有些迷惑,就还要复习一下sql注入的基础。
那现在,我们知道了如下信息:
1)sql语句
$sql = "select * from $db_table where parentid=$parentid";
可以考虑用union语句拼接。
2)数据库表的字段数目
找到city表 ,有5列,
结合前边的,显示位就是cityname ,也就是第三列, 那么语句可以构造为:
parentid=-1 union select 1,2,version(),4,5
http://127.0.0.1/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2,version(),4,5
换一个函数 user()
http://127.0.0.1/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2,user(),4,5
到这里,就跟着书里的思路,复现了这样一个漏洞。
敏感函数回溯参数方法小结:
优点:这种逆向追踪回溯参数的思路,在需要快速寻找漏洞的情况下比较有效。
缺点:不适合运营在企业中的安全运营场景,企业中做自身产品的代码审计时,需要了解整个应用的业务场景,才能发掘到更多有价值的漏洞。
看到这一点,字面意思是不是“随便找代码目录下的文件,然后逐个读完”,感觉这方法是不是有些笨。
其实通读全文代码也有一些技巧,如果只是找文件逐个读完,很难理解整套代码业务逻辑。
以discuz X2.5 为例,下载了一个老版本:
http://download.comsenz.com/DiscuzX/2.5/Discuz\_X2.5\_SC\_UTF8.zip
应该首先看程序的大致代码结构,比如主目录有哪些文件,模块目录有哪些文件,文件命名特点等等。
看代码目录的时候,要注意以下几种文件:
1)函数集文件
关键字:functions、common等,这些文件里边有公共的函数。
一个技巧就是去打开index.php或者一些功能性文件,看它们在文件头部包含了哪些文件。
2)配置文件
关键字:config等
3)安全过滤文件
关键字:filter、safe、check等
4)index文件
入口文件,通常阅读一遍核心目录的index文件,就能大致了解整套程序的架构、流程、包含的文件、核心文件等。
对于各种框架的代码,作者的建议是,学代码审计前期 不建议读开源框架的代码或应用,先找一些小应用来读,等总结了一定经验,对PHP比较熟悉后,再去读像Thinkphp、Yii、Zend Framework 等开源框架。
通读全文代码方法小结:
优点:更好地了解程序的架构和业务逻辑。
缺点:花费时间较多,程序越大约累。
有一些代码审计经验后,就知道哪些功能点对应哪些常见的漏洞,这样快速挖掘漏洞的时候,就可以安装好测试环境,然后配合黑盒测试,来审计常见的功能点。
例如:
1)文件上传功能
2)文件管理功能
3)登录认证功能
4)找回密码功能
然后书中说了一个BugFree老版本重装的问题,问题出现在install\index.php 文件中。
问题代码逻辑:
if(is_file("install.lock") && $action != UPGRADED && $action != INSTALLED)
{
header("location: ../index.php");
}
说这段代码存在一个逻辑漏洞,因为这里仅仅使用了 header(“location: ../index.php”); 后边没有接 die() 或者exit() 等函数退出流程,这个跳转只是HTTP头的跳转,当前PHP文件中,下边的代码依然会继续执行,用浏览器请求 install\index.php 会跳转,用burpsuite 可以看到 接下来的代码执行导致程序再次重装的效果。
我没找到这BugFree的代码,但是这种header的写法 ,很多cms都有,我就改了 另一个CMS代码中的类似点,测了下 header 跳转语句后, 后边如果不接exit 的执行效果,发现确实和书中说的一样,如果header跳转后,不写exit,下边的代码还是会执行,用burpsuite这种 默认不跳转,能看到页面余下代码执行的结果。
恢复 exit
其实这样写的话,即使你用浏览器访问看起来跳转了,那个PHP文件后续的代码也是执行了。
一个简单的例子:
<?php
header('Location: https://sosly.me');
//exit;
echo '5';
sleep(5);
?>
你把这个保存成一个php文件,放到web目录下,然后访问。
发现卡了5秒后跳转到 https://sosly.me ,burpsuite抓包可以看到 返回的数据5:
讲了代码审计的几种常见思路,以及不同方法的适用场景,并练习了几个可操作的实例。
1)根据敏感关键字回溯参数传递过程;
2)查找可控变量,正向追踪变量传递过程;
3)寻找敏感功能点,通读功能点代码;
4)直接通读全文代码。
学到这一节,就是结合例子了解下几种常见的审计思路,还有就是学会了Seay代码审计工具的基本操作。
系列文章,未完待续…
转载请注明出处 :sosly 菜鸟笔记
电脑上也可直接访问博客(**https://sosly.me**)查看,如有内容更新以博客为准。点击原文链接即可跳转。