代码审计需要掌握的点
- PHP编程语言的特性和基础
- Web前端编程基础
- 漏洞形成原理
- 代码审计思路
- 不同系统、中间件之间的特性差异
代码审计思路
- 方法一 ---- 检查
敏感函数的参数
,然后回溯变量
,判断变量是否可控,并且有没有经过严格的过滤,这是一个逆向追踪的过程 - 方法二 ---- 找出
哪些文件在接收外部传入的参数
,然后跟踪变量的传递过程
,观察是否有变量传入到高危函数里面,或者传递的过程是否有逻辑漏洞,这是一种正向追踪的方式 - 方法三 ---- 直接
挖掘功能点漏洞
,根据自身经验判断该类应用通常在哪些功能中会出现漏洞,直接全篇阅读该功能代码
- 方法四 ----
通读全文代码
(1) 方法一
- 检查
敏感函数
的参数,然后回溯变量
,判断变量是否可控,并且有没有经过严格的过滤,这是一个逆向追踪的过程 - 说明:由于大多数漏洞都是由于
函数的使用不当造成的
。而非函数使用不当的漏洞,比如sql注入
,也有一些关键字特征:如select、insert
等。再结合from
和where
等关键字,即可以判断其是否是一条sql语句。再有比如说:http头中的HTTP_CLIENT_IP
和HTTP_X_FORWARDED_FOR
字段,有时在没有过滤的情况下就拼接到了sql语句中,并且由于他们是在$_SERVER
变量中的,不受GPC
的控制。so,我们可以专门去查找http_client_ip
或者http_x_forwarded_for
字段,快速的判断是否有漏洞存在。 - 优点:只需要搜索关键字,可以快速地挖掘出想要的漏洞,可以定向挖掘漏洞。
- 缺点:没有通读代码对程序的整体框架不了解,无法挖掘出逻辑漏洞。在挖掘漏洞时定位漏洞利用点会花费一些时间。
下面介绍一个使用方法一挖掘出来的sql注入漏洞
(参考了seay大佬的《代码审计》一书)
espcms注入挖掘案例,使用的版本是:ESPCMS V6.6.15.12.09
。通过Seay源代码审计系统
导入cms的源代码
进行自动审计
选择第26条内容进行审计($parentid
变量可能存在sql注入漏洞,且通过列表中的漏洞详细信息来看这是一个数字型注入点
),双击这一行可以直接定位到代码所在地。选中$parentid
变量后在下方可以看到变量的传递信息
可以看到$parentid
通过:$parentid = $this->fun->accept('parentid', 'R');
这条代码获得。定位accept函数
的位置
跳转到了class_function.php
函数中
其中的代码如下所示
accept函数
的功能是获取通过GET
、POST
或者COOKIE
方法,传递的变量的值。然后通过daddslashes函数(实际上是包装的addslashes函数,功能差不多)
对传进来的单引号等值进行转义(我们前面说过这是一个数字型注入点,并不需要闭合单引号,因此这个过滤是没有用的)。再通过htmldecode函数
解编码返回传入的数据。这里很明显存在数字型sql注入点
。变量的整个传递过程大概是这样的
确定了存在漏洞之后就要确定漏洞的利用点了。在citylist.php
文件中可以看到该语句$parentid = $this->fun->accept('parentid', 'R');
位于oncitylist函数
中,该函数又位于important类
中。
选中important类
,右键点击全局搜索
可以看到/adminsoft/index.php
文件中有实例化该类
打开查看文件的内容
主要的代码如下所示
$archive = indexget('archive', 'R'); //indexget函数 用来获取通过GET、POST和COOKIE传递的名为archive的参数
$archive = empty($archive) ? 'adminuser' : $archive;
$action = indexget('action', 'R'); //indexget函数 用来获取通过GET、POST和COOKIE传递的名为action的参数
$action = empty($action) ? 'login' : $action;
include admin_ROOT . adminfile . "/control/$archive.php"; // 由于在获取 $archive变量 时经过了indexdaddslashes的过滤因此无法产生文件包含漏洞。因此我们让其包含citylist.php文件
$control = new important(); // 实例化 important类
$action = 'on' . $action; // 给$action 拼接一个 on ,刚好对应了citylist.php中的important类中的oncitylist函数
if (method_exists($control, $action)) {
// 检查实例化后的 $control中是否存在oncitylist函数,肯定是存在的
$control->$action();
} else {
exit('错误:系统方法错误!');
}
关键点:
- 需要通过GET或者POST方式传进来三个参数:
archive
(用于包含citylist.php文件)、action
(用于检测实例化后的对象是否存在oncitylist函数) 和parentid
(写注入语句),让archive
和action
等于citylist。 - 构造EXP:
http://localhost/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 and 1=1
这样去注入即可
(2) 方法二
- 找出哪些
文件
在接收外部传入的参数
,然后跟踪变量的传递过程,观察是否有变量传入到高危函数
里面,或者传递的过程是否有逻辑漏洞,这是一种正向追踪的方式
(3) 方法三
- 直接
挖掘功能点漏洞
,根据自身经验判断该类应用通常在哪些功能中会出现漏洞,直接全篇阅读该功能代码 - 审计方法 ---- 安装好并运行程序,看程序有哪些功能,寻找到实现这些功能的程序文件分别是怎样的。是
独立的模块
,还是插件
,还是写在了一个通用类
中。了解了这些功能的存在形式之后,可先找到经常会出现问题的功能点,黑盒测试一下,如果没有发现常见的漏洞,就去读该功能的代码,进行审计。 - 相关功能点经常会出现的漏洞
- 文件上传功能:文件上传在很多功能点都会有,如:文件编辑、资料编辑、头像上传、附件上传等。该功能点最常见的漏洞就是任意文件上传漏洞。除了任意文件上传之外还经常发生sql注入漏洞,因为程序员一般都不会注意到对文件名进行过滤,但是文件名有时又会保存到数据库中,所以可以通过文件名实现sql注入攻击。
- 文件管理功能:在文件管理功能中,如果程序将
文件名
或者文件路径
直接在参数中传递,那就有可能存在任意文件操作漏洞,如任意文件读取,利用../
或者..\
来跳转路径。还可能存在xss漏洞,程序会在页面中输出文件名,此时如果没有对存入数据库的文件名进行过滤的话就有可能存在xss漏洞。 - 登录认证功能:程序将当前登录的用户账号的认证信息放到
cookie
中,当操作需要认证信息时直接从cookie
中读取用户信息。如果这段cookie没有加salt
一类的东西,就可以导致任意用户登录漏洞
,只要知道用户的部分信息,即可生成认证令牌
。有的程序甚至会直接把用户名明文放到Cookie中,从而导致越权漏洞
。 - 找回密码功能:找回密码功能的漏洞有很多利用场景,常见的是
验证码爆破
。还有验证凭证的算法
,这只有在代码中才能看到,在代码审计时可以看这个算法是否可信。
(4) 方法四
- 直接通读全文代码
- 方法:通读全文代码也有一定的技巧,而非抓住一个文件逐句读完就行了。首先我们要看
程序的大体代码结构
,如:主目录的文件
,模块目录的文件
,插件目录的文件
。除了看有哪些目录还要看文件的大小
和创建时间
。根据文件名就可以推断出文件实现了哪些功能。 - 在看程序目录时需要特别注意的几个文件
- 函数集文件,命名中包含
functions
或者common
等关键字。其中写的是一些公用函数
,是给其他文件统一调用的,大部分的文件中都会在开头包含这些文件。寻找这些文件的方法:打开index.php
或者一些功能文件
,在其头部会包含这些文件。 - 配置文件,命名中通常包含
config
关键字。从其中可以了解到程序的小部分功能。在看配置文件时要看清楚其中参数的值是使用单引号
还是双引号
括起来的,如是双引号则可能存在代码执行漏洞
。 - 安全过滤文件,命名中有
filter
、safe
、check
等关键字。文件的作用是对参数进行过滤。常见的是对:sql注入
、XSS
、文件路径
、执行的系统命令参数
的过滤。很多的程序会在参数传入时使用addslashes()
进行一次过滤。 - index文件,他是程序的入口文件,读一遍
index文件
即可大致了解:程序的架构
、运行的流程
、包含的文件
、核心文件
等信息。不同目录的index文件有不同的实现方式,应当将几个核心目录下的index文件都读一遍。
- 函数集文件,命名中包含
案例:骑士CMS代码审计,版本:3.5.1
第一步:查看应用文件结构
文件目录结构如下所示
首先要看有那些文件和文件夹,寻找名称中带:api,admin,manage,include
关键字的文件,这里有admin
和include
文件夹
查看admin文件夹中的内容,其中有一个api文件夹
再查看include文件夹
中的内容,include文件夹很重要,其中一般会保存比较核心的文件
,供其他文件包含。
第二步:查看关键文件代码
查看include文件夹
下的文件
其中有很多php文件,可以看到有一个名为common.fun.php的文件。前面说过common关键字
多用来命名函数集文件,该文件就是一个定义了许多基础函数的文件。
打开文件第一个函数是addslashes_deep()
function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
if (!get_magic_quotes_gpc()) // 获取magic_quotes_gpc的值
{
$value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags(addslashes($value));
}
else
{
$value=is_array($value) ? array_map('addslashes_deep', $value) : mystrip_tags($value);
}
return $value;
}
}
相关函数:
- get_magic_quotes_gpc() ---- 获取当前
magic_qoutes_gpc
的配置选项设置。本函数自 PHP 7.4.0 起废弃。强烈建议不要使用本函数。返回0
表示magic_quotes_gpc关闭,返回1
代表开启。 - array_map() ---- 为数组的每个元素应用回调函数
该函数首先会判断magic_qoutes_gpc
是否开启,没有开启的话会使用addslashes
【注意:在挖掘sql注入漏洞时除非有宽字节注入
或者二次注入
等特殊情况,只要参数在拼接到sql语句之前调用了addslashes
进行了过滤,就不能进行注入了】进行一次过滤。如果传进的值是数组的话会将数组中的每一个元素作用于addslashes_deep
函数,实际会调用mystrip_tags函数。
mystrip_tags
函数的内容如下。
function mystrip_tags($string)
{
$string = new_html_special_chars($string);
$string = remove_xss($string);
return $string;
}
将传入的值分别作用于new_html_special_chars函数
和remove_xss函数
,这两个函数都是用来过滤XSS的。
首先看看new_html_special_chars
函数中的内容
function new_html_special_chars($string) {
$string = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
$string = strip_tags($string);
return $string;
相关函数:
- strip_tags() ---- 从字符串中去除 HTML 和 PHP 标记
该函数的作用是对输入字符串中的& " > <
进行html实体编码
,然后还使用了strip_tags
函数进行了二次过滤。
remove_xss函数
中的内容如下
function remove_xss($string) {
$string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $string);
$parm1 = Array('javascript', 'union','vbscript', 'expression', 'applet', 'xml', 'blink',