959 views

PHP代码审计菜鸟笔记(二)

image

通过本地搭建 Damn Vulnerable Web Application (DVWA) 学习漏洞的利用和修复思路的时候,就感觉通过阅读 low、medium、high、impossible 的不同等级的存在漏洞缺陷的源代码,可以对漏洞的成因有代码层面的理解,也方便给修复建议的时候和研发进行代码层面的交流。

于是就想系统地学习和练习代码审计这块的知识,目前主要的学习资料就是《代码审计 企业级Web代码安全架构》这本书,PHP语言比较容易上手,环境搭建简单,搞Web的基本都会一点,而且PHP这门语言像腾讯、百度等都广泛运用到了Web上,还是很值得研究的。学习PHP的时候,除了网上的教程,我主要参考了《PHP和MySQL Web开发》这本书。

这里就把我学习的笔记整理成博客,感觉有输出才能更好地巩固学习效果

PHP代码审计菜鸟笔记系列

PHP代码审计菜鸟笔记(一):代码审计前的准备

摘要:学习到这里,就可以开始搭建PHP代码审计的练习环境了,一种比较简单的方式就是在Windows下边安装好phpStudy,然后安装 Notepad++ 编辑器和 Seay源代码审计系统。

PHP代码审计菜鸟笔记(二):通用代码审计思路-本文

摘要:学到这一节,就是结合例子了解下几种常见的审计思路,还有就是学会了Seay代码审计工具的基本操作


本文涉及的工具附件,下载地址如下:

“PHP代码审计菜鸟笔记(一):代码审计前的准备”附件整理

“PHP代码审计菜鸟笔记(二):通用代码审计思路”附件整理

0x01 代码审计工具实现思路

代码审计工具的实现大都是基于代码审计的经验和审计思路,然后把能够自动化的工作尽可能自动化,辅助提高审计效率。

我看了下“Seay源代码审计系统”的默认正则匹配规则,如下:

image

下边也可以填写测试数据,在工具里验证。

image

配置文件里也能找到这些规则的文本,感兴趣的可以研究,默认规则文本整理如下:

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}\(    存在文件上传,注意上传类型是否可控

0x02 常见的代码审计思路

1)根据敏感关键字回溯参数传递过程;
2)查找可控变量,正向追踪变量传递过程;
3)寻找敏感功能点,通读功能点代码;
4)直接通读全文代码

下边会结合具体的实例来练习。

0x03 敏感函数回溯参数过程

例如:

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”,安装过程中自己设置管理员密码。安装后可以访问后台:

http://127.0.0.1/espcms/adminsoft/index.php

image

登陆后是这个样子

image

这样基本的审计环境就搭好了,顺便我也安装了 Seay源代码审计系统。

2)利用Seay源代码审计系统 审计一个功能点

可以看着一起操作,我步骤写的很细致。这个例子也是“Seay源代码审计系统”的入门教学。

先“新建项目”载入程序,然后自动审计,注意文件夹选择 安装后的Web目录下的那个espcms的文件夹

image

我这里是第28行,这个文件,说的是里边的变量$parentid 无单引号保护,可能存在注入

/adminsoft/control/citylist.php

双击,跳转到代码:

image

image

然后左边可以跟踪变量,选中$parentid就看到这个变量的在当前文件的传递过程:

1)$parentid = $this->fun->accept('parentid', 'R');   
2)$sql = "select * from $db_table where parentid=$parentid";

接下来定位函数

image

image

双击点进去

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 里,就看哪里实例化了这个例,目的是找到调用这个函数的地方。

image

看到 /adminsoft/index.php 这里有:

image

image

这里读一下代码,涉及到2个参数 分别是 archiveaction,初学的时候可能有些懵逼,我们不妨在未登录状态下,先访问

http://127.0.0.1/espcms/adminsoft/index.php

看看情况,发现默认情况下,URL会自动跳转到:

http://127.0.0.1/espcms/adminsoft/index.php?archive=adminuser&action=login

image

这和代码里的

$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() 函数.

image

那么问题来了,我们的目的是触发 /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

image

由于这个注入点,在/espcms/adminsoft/ 目录下,是需要管理员登录后才可访问的,所以我们先登录,再来测试这个注入点。

3)审计结果的验证

http://127.0.0.1/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=1

加一个单引号:

image

image

直接报错了。

如果有黑盒测试经验的话,这个时候用salmap跑就比较方便。

但是咱们这次是白盒审计,SQL语句、代码、数据库啥都能看到,我们就练习下白盒审计的思路。这部分要是有些迷惑,就还要复习一下sql注入的基础。

那现在,我们知道了如下信息:

1)sql语句

$sql = "select * from $db_table where parentid=$parentid";

可以考虑用union语句拼接。

2)数据库表的字段数目

找到city表 ,有5列,

image

结合前边的,显示位就是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

image

换一个函数 user()

http://127.0.0.1/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2,user(),4,5

image

到这里,就跟着书里的思路,复现了这样一个漏洞。

敏感函数回溯参数方法小结

优点:这种逆向追踪回溯参数的思路,在需要快速寻找漏洞的情况下比较有效。
缺点:不适合运营在企业中的安全运营场景,企业中做自身产品的代码审计时,需要了解整个应用的业务场景,才能发掘到更多有价值的漏洞。

0x04 通读全文代码

看到这一点,字面意思是不是“随便找代码目录下的文件,然后逐个读完”,感觉这方法是不是有些笨。

其实通读全文代码也有一些技巧,如果只是找文件逐个读完,很难理解整套代码业务逻辑

以discuz X2.5 为例,下载了一个老版本:

http://download.comsenz.com/DiscuzX/2.5/Discuz_X2.5_SC_UTF8.zip

应该首先看程序的大致代码结构,比如主目录有哪些文件,模块目录有哪些文件,文件命名特点等等。

image

看代码目录的时候,要注意以下几种文件:

1)函数集文件

关键字:functions、common等,这些文件里边有公共的函数。

一个技巧就是去打开index.php或者一些功能性文件,看它们在文件头部包含了哪些文件。

image

2)配置文件

关键字:config等

image

3)**安全过滤文件 **

关键字:filter、safe、check等

image

image

4)index文件

入口文件,通常阅读一遍核心目录的index文件,就能大致了解整套程序的架构、流程、包含的文件、核心文件等。

image

image

对于各种框架的代码,作者的建议是,学代码审计前期 不建议读开源框架的代码或应用,先找一些小应用来读,等总结了一定经验,对PHP比较熟悉后,再去读像Thinkphp、Yii、Zend Framework 等开源框架

通读全文代码方法小结

优点:更好地了解程序的架构和业务逻辑。
缺点:花费时间较多,程序越大约累。

0x05 根据功能点的定向审计

有一些代码审计经验后,就知道哪些功能点对应哪些常见的漏洞,这样快速挖掘漏洞的时候,就可以安装好测试环境,然后配合黑盒测试,来审计常见的功能点

例如:

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这种 默认不跳转,能看到页面余下代码执行的结果。

image

image

恢复 exit

image

其实这样写的话,即使你用浏览器访问看起来跳转了,那个PHP文件后续的代码也是执行了

一个简单的例子:

<?php
header('Location: https://sosly.me');
//exit;

echo '5';
sleep(5);

?>

你把这个保存成一个php文件,放到web目录下,然后访问。

发现卡了5秒后跳转到 https://sosly.me ,burpsuite抓包可以看到 返回的数据5:

image

0x06 小结

讲了代码审计的几种常见思路,以及不同方法的适用场景,并练习了几个可操作的实例。

1)根据敏感关键字回溯参数传递过程;
2)查找可控变量,正向追踪变量传递过程;
3)寻找敏感功能点,通读功能点代码;
4)直接通读全文代码。

学到这一节,就是结合例子了解下几种常见的审计思路,还有就是学会了Seay代码审计工具的基本操作

系列文章,未完待续...


转载请注明出处 :sosly 菜鸟笔记
https://sosly.me/index.php/2018/04/03/php_daimashenji2/

也欢迎关注微信公众号:sosly菜鸟笔记
sosly菜鸟笔记微信公众号

One thought on “PHP代码审计菜鸟笔记(二)

发表评论

电子邮件地址不会被公开。 必填项已用*标注