高级PHP应用程序漏洞审核技术
?
- 高级PHP应用程序漏洞审核技术
- 前言
- 传统的代码审计技术
- PHP版本与应用代码审计
- 其他的因素与应用代码审计
- 扩展我们的字典
- 变量本身的key
- 变量覆盖
- 遍历初始化变量
- parse_str()变量覆盖漏洞
- import_request_variables()变量覆盖漏洞
- PHP5 Globals
- magic_quotes_gpc与代码安全
- 什么是magic_quotes_gpc
- 哪些地方没有魔术引号的保护
- 变量的编码与解码
- 二次攻击
- 魔术引号带来的新的安全问题
- 变量key与魔术引号
- 代码注射
- PHP中可能导致代码注射的函数
- 变量函数与双引号
- PHP自身函数漏洞及缺陷
- PHP函数的溢出漏洞
- PHP函数的其他漏洞
- session_destroy()删除文件漏洞
- 随机函数
- 特殊字符
- 截断
- include截断
- 数据截断
- 文件操作里的特殊字符
- 怎么进一步寻找新的字典
- DEMO
- 后话
- 附录
?
前言
PHP是一种被广泛使用的脚本语言,尤其适合于web开发。具有跨平台,容易学习,功能强大等特点,据统计全世界有超过34%的网站有php的应用,包括Yahoo、sina、163、sohu等大型门户网站。而且很多具名的web应用系统(包括bbs,blog,wiki,cms等等)都是使用php开发的,Discuz、phpwind、phpbb、vbb、wordpress、boblog等等。随着web安全的热点升级,php应用程序的代码安全问题也逐步兴盛起来,越来越多的安全人员投入到这个领域,越来越多的应用程序代码漏洞被披露。针对这样一个状况,很多应用程序的官方都成立了安全部门,或者雇佣安全人员进行代码审计,因此出现了很多自动化商业化的代码审计工具。也就是这样的形势导致了一个局面:大公司的产品安全系数大大的提高,那些很明显的漏洞基本灭绝了,那些大家都知道的审计技术都无用武之地了。我们面对很多工具以及大牛扫描过n遍的代码,有很多的安全人员有点悲观,而有的官方安全人员也非常的放心自己的代码,但是不要忘记了“没有绝对的安全”,我们应该去寻找新的途径挖掘新的漏洞。本文就给介绍了一些非传统的技术经验和大家分享。
另外在这里特别说明一下本文里面很多漏洞都是来源于网络上牛人和朋友们的分享,在这里需要感谢他们 :)
传统的代码审计技术
WEB应用程序漏洞查找基本上是围绕两个元素展开:变量与函数。也就是说一漏洞的利用必须把你提交的恶意代码通过变量经过n次变量转换传递,最终传递给目标函数执行,还记得MS那句经典的名言吗?“一切输入都是有害的”。这句话只强调了变量输入,很多程序员把“输入”理解为只是gpc[$_GET,$_POST,$_COOKIE],但是变量在传递过程产生了n多的变化。导致很多过滤只是个“纸老虎”!我们换句话来描叙下代码安全:“一切进入函数的变量是有害的”。
PHP代码审计技术用的最多也是目前的主力方法:静态分析,主要也是通过查找容易导致安全漏洞的危险函数,常用的如grep,findstr等搜索工具,很多自动化工具也是使用正则来搜索这些函数。下面列举一些常用的函数,也就是下文说的字典(暂略)。但是目前基本已有的字典很难找到漏洞,所以我们需要扩展我们的字典,这些字典也是本文主要探讨的。
其他的方法有:通过修改PHP源代码来分析变量流程,或者hook危险的函数来实现对应用程序代码的审核,但是这些也依靠了我们上面提到的字典。
PHP版本与应用代码审计
到目前为止,PHP主要有3个版本:php4、php5、php6,使用比例大致如下:
?
php4 | 68% | 2000-2007,No security fixes after 2008/08,最终版本是php4.4.9 |
php5 | 32% | 2004-present,Now at version 5.2.6(PHP 5.3 alpha1 released!) |
php6 | ? | 目前还在测试阶段,变化很多做了大量的修改,取消了很多安全选项如magic_quotes_gpc(这个不是今天讨论的范围) |
?
由于php缺少自动升级的机制,导致目前PHP版本并存,也导致很多存在漏洞没有被修补。这些有漏洞的函数也是我们进行WEB应用程序代码审计的重点对象,也是我们字典重要来源。
其他的因素与应用代码审计
很多代码审计者拿到代码就看,他们忽视了“安全是一个整体”,代码安全很多的其他因素有关系,比如上面我们谈到的PHP版本的问题,比较重要的还有操作系统类型(主要是两大阵营win/*nix),WEB服务端软件(主要是iis/apache两大类型)等因素。这是由于不同的系统不同的WEB SERVER有着不同的安全特点或特性,下文有些部分会涉及。
所以我们在做某个公司WEB应用代码审计时,应该了解他们使用的系统,WEB服务端软件,PHP版本等信息。
扩展我们的字典
下面将详细介绍一些非传统PHP应用代码审计一些漏洞类型和利用技巧。
变量本身的key
说到变量的提交很多人只是看到了GET/POST/COOKIE等提交的变量的值,但是忘记了有的程序把变量本身的key也当变量提取给函数处理。
php//key.php?aaaa'aaa=1&bb'b=2 //print_R($_GET); ?foreach($_GET AS $key => $value){? ? ? ? print $key."\n";}?>
上面的代码就提取了变量本身的key显示出来,单纯对于上面的代码,如果我们提交URL:
key.php?script>alert(1);script>=1&bbb=2
那么就导致一个xss的漏洞,扩展一下如果这个key提交给include()等函数或者sql查询呢?:)
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:通读代码 |
?
变量覆盖
很多的漏洞查找者都知道extract()这个函数在指定参数为EXTR_OVERWRITE或者没有指定函数可以导致变量覆盖,但是还有很多其他情况导致变量覆盖的如:
遍历初始化变量
请看如下代码:
php//var.php?a=fuck$a='hi';foreach($_GET as $key => $value){? ? ? ? $$key = $value;}print $a;?>
很多的WEB应用都使用上面的方式(注意循环不一定是foreach),如Discuz!4.1的WAP部分的代码:
$chs ='';if($_POST && $charset !='utf-8'){? ? ? ? $chs =newChinese('UTF-8', $charset);? ? ? ? foreach($_POST as $key => $value){? ? ? ? ? ? ? ? $$key = $chs->Convert($value);? ? ? ? }? ? ? ? unset($chs);
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:通读代码 |
?
parse_str()变量覆盖漏洞
//var.php?var=new$var ='init'; ? ? ? ? ? ? ? ? ? ? parse_str($_SERVER['QUERY_STRING']); print $var;
该函数一样可以覆盖数组变量,上面的代码是通过$_SERVER['QUERY_STRING']来提取变量的,对于指定了变量名的我们可以通过注射“=”来实现覆盖其他的变量:
//var.php?var=1&a[1]=var1%3d222$var1 ='init';parse_str($a[$_GET['var']]);print $var1;
上面的代码通过提交$var来实现对$var1的覆盖。
?
漏洞审计策略(parse_str) |
PHP版本要求:无 系统要求:无 审计策略:查找字符parse_str |
?
?
漏洞审计策略(mb_parse_str) |
PHP版本要求:php4系统要求:无 审计策略:查找字符mb_parse_str |
?
import_request_variables()变量覆盖漏洞
//var.php?_SERVER[REMOTE_ADDR]=10.1.1.1echo 'GLOBALS '.(int)ini_get("register_globals")."n";import_request_variables('GPC');if($_SERVER['REMOTE_ADDR']!='10.1.1.1')die('Go away!');echo 'Hello admin!';
?
漏洞审计策略(import_request_variables) |
PHP版本要求:php4系统要求:无 审计策略:查找字符import_request_variables |
?
PHP5 Globals
从严格意义上来说这个不可以算是PHP的漏洞,只能算是一个特性,测试代码:
// register_globals =ON//foo.php?GLOBALS[foobar]=HELLOphp echo $foobar; ?>
但是很多的程序没有考虑到这点,请看如下代码:
//为了安全取消全局变量//var.php?GLOBALS[a]=aaaa&b=111if(ini_get('register_globals'))foreach($_REQUEST as $k=>$v) unset(${$k});print $a;print $_GET[b];
如果熟悉WEB2.0的攻击的同学,很容易想到上面的代码我们可以利用这个特性进行crsf攻击。
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:通读代码 |
?
magic_quotes_gpc与代码安全
?
什么是magic_quotes_gpc
当打开时,所有的 '(单引号),"(双引号),\(反斜线)和 NULL 字符都会被自动加上一个反斜线进行转义。还有很多函数有类似的作用 如:addslashes()、mysql_escape_string()、mysql_real_escape_string()等,另外还有parse_str()后的变量也受magic_quotes_gpc的影响。目前大多数的主机都打开了这个选项,并且很多程序员也注意使用上面那些函数去过滤变量,这看上去很安全。很多漏洞查找者或者工具遇到些函数过滤后的变量直接就放弃,但是就在他们放弃的同时也放过很多致命的安全漏洞。 :)
哪些地方没有魔术引号的保护
1) $_SERVER变量
PHP5的$_SERVER变量缺少magic_quotes_gpc的保护,导致近年来X-Forwarded-For的漏洞猛暴,所以很多程序员考虑过滤X-Forwarded-For,但是其他的变量呢?
?
漏洞审计策略($_SERVER变量) |
PHP版本要求:无 系统要求:无 审计策略:查找字符_SERVER |
?
2) getenv()得到的变量(使用类似$_SERVER变量)
?
漏洞审计策略(getenv()) |
PHP版本要求:无 系统要求:无 审计策略:查找字符getenv |
?
3) $HTTP_RAW_POST_DATA与PHP输入、输出流
主要应用与soap/xmlrpc/webpublish功能里,请看如下代码:
if(!isset( $HTTP_RAW_POST_DATA )){? ? ? ? $HTTP_RAW_POST_DATA = file_get_contents('php://input');}if( isset($HTTP_RAW_POST_DATA))? ? ? ? $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
?
漏洞审计策略(数据流) |
PHP版本要求:无 系统要求:无 审计策略:查找字符HTTP_RAW_POST_DATA或者php://input |
?
4) 数据库操作容易忘记'的地方如:in()/limit/order by/group by
如Discuz!
if(is_array($msgtobuddys)){? ? ? ? $msgto = array_merge($msgtobuddys, array($msgtoid));? ? ? ? ? ? ? ? ......foreach($msgto as $uid){? ? ? ? $uids .= $comma.$uid;? ? ? ? $comma =',';}......$query = $db->query("SELECT m.username, mf.ignorepm FROM {$tablepre}members m? ? ? ? LEFT JOIN {$tablepre}memberfields mf USING(uid)? ? ? ? WHERE m.uid IN ($uids)");
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:查找数据库操作字符(select,update,insert等等) |
?
变量的编码与解码
一个WEB程序很多功能的实现都需要变量的编码解码,而且就在这一转一解的传递过程中就悄悄的绕过你的过滤的安全防线。
这个类型的主要函数有:
1) stripslashes() 这个其实就是一个decode-addslashes()
2) 其他字符串转换函数:
?
base64_decode | 对使用 MIME base64 编码的数据进行解码 |
base64_encode | 使用 MIME base64 对数据进行编码 |
rawurldecode | 对已编码的 URL 字符串进行解码 |
rawurlencode | 按照 RFC 1738 对 URL 进行编码 |
urldecode | 解码已编码的 URL 字符串 |
urlencode | 编码 URL 字符串 |
... | ... |
?
另外一个 unserialize/serialize
3) 字符集函数(GKB,UTF7/8...)如iconv()/mb_convert_encoding()等
目前很多漏洞挖掘者开始注意这一类型的漏洞了,如典型的urldecode:
$sql ="SELECT * FROM article WHERE articleid='".urldecode($_GET[id])."'";
当magic_quotes_gpc=on时,我们提交?id=%2527,得到sql语句为:
SELECT * FROM article WHERE articleid='''
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:查找对应的编码函数 |
?
二次攻击
详细见附录[1]
1)数据库出来的变量没有进行过滤
2)数据库的转义符号:
- mysql/oracle转义符号同样是\(我们提交'通过魔术引号变化为\',当我们update进入数据库时,通过转义变为')
- mssql的转义字符为'(所以我们提交'通过魔术引号变化为\',mssql会把它当为一个字符串直接处理,所以魔术引号对于mssql的注射没有任何意义)
从这里我们可以思考得到一个结论:一切进入函数的变量都是有害的,另外利用二次攻击我们可以实现一个webrootkit,把我们的恶意构造直接放到数据库里。我们应当把这样的代码看成一个vul?
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:通读代码 |
?
魔术引号带来的新的安全问题
首先我们看下魔术引号的处理机制:
[\-->\\,'-->\',"-->\",null-->\0]
这给我们引进了一个非常有用的符号“\”,“\”符号不仅仅是转义符号,在WIN系统下也是目录转跳的符号。这个特点可能导致php应用程序里产生非常有意思的漏洞:
1)得到原字符(',\,",null])
$order_sn=substr($_GET['order_sn'],1);//提交 ? ? ? ? ? ? ? ? '//魔术引号处理 ? ? ? ? \'//substr ? ? ? ? ? ? ? '$sql ="SELECT order_id, order_status, shipping_status, pay_status, ".? ?" shipping_time, shipping_id, invoice_no, user_id ".? ?" FROM ". $ecs->table('order_info').? ?" WHERE order_sn = '$order_sn' LIMIT 1";
2)得到“\”字符
$order_sn=substr($_GET['order_sn'],0,1);//提交 ? ? ? ? ? ? ? ? '//魔术引号处理 ? ? ? ? \'//substr ? ? ? ? ? ? ? \ ? ?$sql ="SELECT order_id, order_status, shipping_status, pay_status, ".? ?" shipping_time, shipping_id, invoice_no, user_id ".? ?" FROM ". $ecs->table('order_info').? ?" WHERE order_sn = '$order_sn' and order_tn='".$_GET['order_tn']."'";
提交内容:
?order_sn='&order_tn=%20and%201=1/*
执行的SQL语句为:
SELECT order_id, order_status, shipping_status, pay_status, shipping_time, shipping_id, invoice_no, user_id FROM order_info WHERE order_sn ='\' and order_tn='and1=1/*'
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:查找字符串处理函数如substr或者通读代码 |
?
变量key与魔术引号
我们最在这一节的开头就提到了变量key,PHP的魔术引号对它有什么影响呢?
php//key.php?aaaa'aaa=1&bb'b=2 //print_R($_GET); ?foreach($_GET AS $key => $value)? ? ? ? {? ? ? ? print $key."\n";? ? ? ? }?>
1)当magic_quotes_gpc = On时,在php5.24下测试显示:
aaaa\'aaabb\'b
从上面结果可以看出来,在设置了magic_quotes_gpc = On下,变量key受魔术引号影响。但是在php4和php
php//key.php?aaaa'aaa[bb']=1 print_R($_GET); ?>
结果显示:
Array([aaaa'aaa] => Array ( [bb\'] => 1 ) )
数组第一维变量的key不受魔术引号的影响。
?
漏洞审计策略 |
PHP版本要求:php4和php系统要求:无 审计策略:通读代码 |
?
2)当magic_quotes_gpc = Off时,在php5.24下测试显示:
aaaa'aaabb'b
对于magic_quotes_gpc = Off时所有的变量都是不安全的,考虑到这个,很多程序都通过addslashes等函数来实现魔术引号对变量的过滤,示例代码如下:
php //keyvul.php?aaa'aa=1'//magic_quotes_gpc = Off?if(!get_magic_quotes_gpc()){?$_GET ?= addslashes_array($_GET);}function addslashes_array($value){? ? ? ? return is_array($value)? array_map('addslashes_array', $value): addslashes($value);}print_R($_GET);foreach($_GET AS $key => $value){? ? ? ? print $key;}?>
以上的代码看上去很完美,但是他这个代码里addslashes($value)只处理了变量的具体的值,但是没有处理变量本身的key,上面的代码显示结果如下:
Array(? ? [aaa'aa] => 1\')aaa'aa
?
漏洞审计策略 |
PHP版本要求:无 系统要求:无 审计策略:通读代码 |
?
代码注射
PHP中可能导致代码注射的函数
很多人都知道eval、preg_replace+/e可以执行代码,但是不知道php还有很多的函数可以执行代码如:
?
assert() |
call_user_func() |
call_user_func_array() |
create_function() |
变量函数 |
... |
?
这里我们看看最近出现的几个关于create_function()代码执行漏洞的代码:
php//how to exp this code$sort_by=$_GET['sort_by'];$sorter='strnatcasecmp';$databases=array('test','test');$sort_function =' ?return 1 * '. $sorter .'($a["'. $sort_by .'"], $b["'. $sort_by .