php代码审计基础篇
文章最后更新时间为:2019年01月20日 22:08:44
0. 前言
打算寒假学习代码审计和pwn,但是pwn真的是对人太不友好了,在这靠抖取暖的寒冬里大概看完了尹毅的《代码审计 企业级web代码安全架构》,虽然书有好几年了,但是里面的漏洞挖掘思路还是很友好的。写的一气呵成,看的也很过瘾,这里记录一波,打好基础。
本篇为基础篇,介绍常见的SQL注入,XSS,CSRF、文件操作、代码执行、命令执行等漏洞的挖掘思路和防范策略。很多细节没写,可以写,但没必要。
1. SQL注入
sql注入最为常见,一般出现在登陆界面、表单界面、http中的GET参数,x-forward-for等,另外在订单系统还容易发生二次注入,审计时需要关注这几个地方。
1.1 普通注入
对于与数据库交互的数据没有任何防护,这种低级错误比较少见,普通注入有int型和string型,对于这些我们只需要查找一些关键函数即可,比如mysql_connect
、mysql_query
、select from
、mysql_fetch_row
、update
、insert
、delete
等,我们做白盒审计的时候,只需要查找这些关键词即可定向挖掘SQL注入漏洞。
1.2 宽字节注入
在使用PHP连接数据库的时候,当设置set character_set_client=gbk
时会导致一个编码转换的注入问题,也就是宽字节注入。
当存在宽字节注入时,注入参数里的%df%27
会将过滤的\(%5c)
吃掉。举个例子,当/1.php?id=1
存在注入,我们输入/1.php?id=1' and 1=1#
,MYSQL执行的是
select * from user where id = '1\' and 1 = 1#'
显然单引号没有闭合,但是当我们提交1.php?id=1%df' and 1 = 1#
时,这时MYSQL运行的是
select * from user where id = '1運' and 1 = 1#'
单引号闭合,注入成功,造成这种结果的原因是客户端编码为GBK,MYSQL服务器对查询语句进行转码的过程中,将%df%5c变成了一个汉字“運”,造成了引号的逃逸。
通常php设置编码的方式不是set character_set_client=gbk
而是SET NAMES 'gbk'
,SET NAMES 'gbk'
等同于以下代码
SET
character_set_connection = 'gbk',
character_set_result = 'gbk',
character_set_client = gbk
不过这也存在漏洞,官方给出的建议是使用mysql_set_charset
来设置编码,不过它也是调用了SET NAMES
,效果差不多。
对宽字节注入的挖掘比较简单,搜索以下几个关键词即可:
SET NAMES
character_set_client=gbk
mysql_st_charset('gbk')
关于宽字节注入有以下几种解决方式:
- 客户端使用utf-8编码
- 在执行查询之前先执行
SET NAMES 'gbk'
, 再设置character_set_client = binary
- 使用
mysql_set_charset('gbk')
设置编码,然后使用mysql_real_escape_string()
函数进行参数过滤 - 使用PDO方式查询
关于利用字符编码注入可以参考 https://www.freebuf.com/articles/web/31537.html
1.3 二次urldecode注入
如果网站某处使用了urldecode
或者rawurldecode
函数,则又可能导致二次注入。比如当网站先采用addslashes()、mysql_real_escape_string()、mysql_escape_string()函数对字符串进行过滤,然后再对字符串进行解码,这样就会造成二次注入。
测试代码:
<?php
$a = addslashes($_GET['p']);
$b = urldecode($a);
echo '$a ='.$a;
echo '<br />';
echo '$b ='.$b;
原因是当我们提交参数到webserver时,webserver会自动进行解码一次,%25解码后是%,%25%27解码后也就是%27,然后再解码一次,%27解码为单引号。
1.4 防范
目前主要的防范方法有:
- gpc/rutime魔术引号
主要涉及配置文件参数magic_quotes_gpc
和magic_quotes_runtime
,前者负责对GET、POST、COOKIE的值进行过滤,后者对从数据库或者文件中获取的数据进行过滤,但是只对单引号、双引号、反斜杠和空字符进行过滤,在int型注入上没多大作用。 - 使用过滤函数
- addslashes函数
- mysql_[real_]escape_string函数
- intval等字符转换
关于以上函数,这里就不多说了 - PDO prepare预编译
2. XSS漏洞
2.1 漏洞挖掘
XSS漏洞比SQL注入更多,也更难防,经常出现在文章发表、评论回复、留言以及资料设置等地方,在通读代码的时候可以重点关注这几个地方。
挖掘XSS的漏洞关键在于寻找没有被过滤的参数,且这些参数传输到输出函数,常见的输出函数有print、print_r、printf、spintf、echo、die、var_dump、var_export,所以我们只要寻找带有变量的这些函数即可。
2.2 漏洞防范
2.2.1 特殊字符HTML实体转码
一般的XSS漏洞都是没有过滤特殊符号,比如<img src = "$_GET['a']"/>
,这里只需要先输入双引号就可以提前闭合利用漏洞,防御这类的XSS一般对特殊字符进行过滤即可。常见的有单引号、双引号、尖括号、反斜杠、冒号、and符号、#。为了保证数据的完整性,最好采用HTML转码。
2.2.2 标签事件黑白名单
过滤字符仍有可能被利用,如宽字节转码可以吃掉反斜杠,面对这种情况可以采用黑/白名单。
3. CSRF漏洞
3.1 漏洞挖掘
CSRF主要是用于越权操作,所有漏洞自然在有权限控制的地方,像管理后台、会员中心、论坛帖子以及交易管理等地方。
我们在漏洞挖掘的时候可以先搭建好环境,打开几个有非静态操作的页面,抓包看有没有token,如果没有token的话在直接请求页面,不带referer,如果返回的数据还是一样的话说明很可能存在csrf漏洞。
从白盒角度来说,主要是看核心文件里面有没有验证token和referer相关的代码。这里的核心文件指的是被大量引用的基础文件,或者直接搜token这个关键字,如果核心文件里面没有,再去看比较重要的功能点的代码。
3.2 漏洞防范
防御CSRF漏洞的最主要问题是解决可信的问题,即使是管理员权限提交到服务器的数据,也不一定是完全可信的,所以针对CSRF的防御有以下两点:
- 增加token/referer验证避免img标签请求的水坑攻击
- 增加验证码
4. 文件操作漏洞
文件操作包含文件包含、文件读取、文件删除、文件修改以及文件上传等。且一一道来?
4.1 文件包含
文件包含大多出现在模块加载的地方,比如传入的模块名参数,实际上是直接把这个拼接到了包含文件的路径中,比如espcms的代码:
$archive = indexget('archive','R');
$archive = empty($archive) ? 'adminuser':'$archive;
$action = indexget('action','R');
$action = empty($action) ? 'login':$action;
include admin_ROOT . adminfile . "/control/$archive.php";
传入的$archive就是被包含的文件名,所以我们在挖掘文件包含漏洞的时候可以先跟踪一下程序运行流程,看看文件名是否可控,另外就是直接搜索include()
、include_once()
、require()
、require_once()
这四个函数来回溯看看有没有可控的变量。
文件的包含截断
大多数的文件包含漏洞都是需要截断的,正常的文件代码都是像include(BASEPATH.$mod.'php')
这样的方式,如果我们不能写入以php结尾的文件时,那我们就要用到阶段,下面说下几种截断当时
1.利用%00截断,方法很古老,受限于GPC和addslashes的过滤,在php5.3之后的版本中也会失败,所以这基本是个鸡肋方法。
使用举例:
<?php
include $_GET['a'].'.php'
?>
请求 http://localhost/test/1.php?a=2.txt%00
2.利用多个英文句号(.)和反斜杠(/)来截断,这种方法不受GPC限制,不过同样在PHP5.3版本之后被修复。
测试代码:
<?php
$str = '';
for($i=0;$i<=240;$i++){
$i.='.';
}
$str = '2.txt'.$str;
echo $str;
include $str.'.php';
windows下测试是240个连续的点(.)或者点加斜杠(./)能够截断,linux下是2038个斜杠加点能够截断(/.)
3.远程文件包含时利用问号来伪截断,不受FPC和PHP版本限制。
在HTTP协议里面,访问http://remotehost/1.txt
和访问http://remotehost/1.txt?php
返回的结果是一样的.webserver把问号之后的内容当成是请求参数,而txt不再webserver里面解析,参数对访问txt返回的内容不影响。所以就实现了伪截断。
4.2 文件读取和下载
文件读取漏洞和下载漏洞差别不大,挖掘起来也很容易,一种方式是可以先黑盒看看功能点对应的文件,再去读文件,另外一种方式就是去搜索文件读取的函数,看看有没有可控的变量,文件读取函数有:file_get_content()
、highlight_file()
、fopen()
、readfile()
、fread()
、fgetss()
、fgets()
、parse_ini_file()
、show_source()
、file()
,除了这些之外,还有一些函数,比如文件包含函数include等,可以利用PHP输入输出流php://filter
来读取文件。
4.3 文件上传漏洞
文件上传漏洞挖掘起来很简单,一般应用上传点比较少,上传函数只有move_uploaded_file()这一个,所以审计的时候直接搜索这个函数即可,查看文件名能否绕过,问题比较多的是黑名单限制以及未更改上传文件名的方式,未改名一般可以利用解析漏洞。其次涉及到黑名单的绕过,content-type和文件头的绕过。
4.4 文件删除漏洞
挖掘文件删除漏洞可以先去找相应的功能点,直接黑盒测试一下看能不能删除文件,如果删除不了,再去从执行流程追踪文件名的传递参数,也可以搜索带变量参数的unlink()。
5. 代码执行漏洞
5.1 漏洞挖掘
该漏洞主要由eval()
、assert()
、preg_replace()
、call_user_func()
、call_user_func_array()
、array_map()
等函数的过滤不严格导致的,另外还有php的动态函数($a($b))也是目前出现比较多的。
- eval()和assert()导致的漏洞大多是因为载入缓存或者模板以及对变量的处理不严格导致,比如把一个可控的参数拼接到模板里面,再当成代码执行。
- preg_replace()函数的代码执行需要/e参数,这个函数本来是用来处理字符串的,因此漏洞出现最多的地方是在对字符串的处理,比如url、html标签以及文章内容的过滤。
- call_user_func()和call_user_func_array()函数的功能是调用函数,多用于框架里面动态调用函数,小程序里面出现这种当时的代码执行会比较少。如果参数可控,那就可以调用意外的函数。
- 动态函数的代码执行,比如
$_GET($_POST['xxx'])
,基于这种写法变形出来的各种异形,常被用做web后门使用。要挖掘这种形式的代码执行漏洞,需要寻找可控的动态函数名。
5.2 漏洞防范
采用参数白名单过滤即可,可采用正则表达式来限制参数。
6. 命令执行漏洞
6.1 漏洞挖掘
命令执行漏洞是可以执行系统或者应用指令(如cmd或者bash命令),最多出现在包含环境包的应用里面,类似于eyou这类产品,直接在系统安装即可启动自带的web服务和数据库服务。
web应用中会有比较多的点之间使用system()
、exec()
、shell_exec()
、passthru()
、popen()
、proc_open()
等函数直接执行系统命令来调用这些脚本,可以直接在代码中搜这几个函数,收获应该会不少。
除了这类应用,还有像discus等应用也有直接调用外部程序的功能,如数据库导出功能,曾经就出现过命令执行漏洞,因为特征比较明显,所以可以直接搜函数名即可进行漏洞挖掘。
另外反引号也可以直接执行命令,比如
<?php
echo `whoami`;
6.2 漏洞防范
php在SQL防注入上有addslashes()
、mysql_[real_]escape_string()
等函数,在命令上也有防注入函数,一共两个escapeshellcmd()和escapeshellarg(),前者过滤整条命令,后者过滤参数,细节略。
或者采用参数白名单,在使用的时候匹配一下参数是否在白名单,在大多数参数过滤时都很好用。
7. 总结
No summary
学习了,涨姿势啊!!