很难找到一个密码学的实例,就拿上一期的xor挑战来讲咯
感谢基友@overlord和我一起努力俩天才干掉这个题
先上代码吧
"; } } } } if (!$_SESSION[\'uid\']) { echo "
"; }else{ echo $msg; } ?>
0x01 构造cookie注入
先忽略加密的部分,看看后面
if ($_COOKIE[\'auth\']){ list($user, $password) = explode("\t", authcode($_COOKIE[\'auth\'], \'DECODE\', $secret_key)); if ($user !=\'\' && $password != \'\'){ $sql = "select uid, username, password from users where username=\'$user\'"; $result = mysql_query($sql); if ($result){ $row = @mysql_fetch_array($result); if ($row[\'password\']===md5($password)){ $_SESSION[\'uid\'] = $row[\'uid\']; echo "
"; } } } }
user和pass是解密出来后用\t来分割得到的,然后进入sql查询
查到的md5和输入的对比,过了以后输出用户名和msg
很简单的注入 主要解密出来的是这样的字符串就可以了 最后这个\t可以不加
\' union select 1,2,\'eccbc87e4b5ce2fe28308fd9f2a7baf3\'#\t3\t
来看看效果
这样就可以通过验证了
0x02 算法分析
好的 现在进入正题 看看算法的逻辑
由$secret_key产生32位$keya与32位$keyb
加密的时候 $keyc是根据时间戳生成随机8位字符
$cryptkey是$keya.md5($keya.$keyc)的sha256 //包含了$keyc也就是说每次请求都会变化
新的$string是10个0加上md5($keyb.$string).$string
俩者异或之后输出$keyc加上base64编码的密文
解密的时候 $keyc取传入$string的前8位
$cryptkey是$keya.md5($keya.$keyc)的sha256 //由于$keyc是获取的 所以和加密时的$cryptkey相同
$string是传如$string的除了前8位的字符解base64编码 //也就是加密时的$result
两者异或得到$result
由于异或算法是对合的 通俗的讲 $string xor $cryptkey xor $cryptkey = $string
所以解密得到的是加密时的$string 这一点非常重要
可能看文字 有同学会有点懵逼了 我们来画张图(请原谅我不会用绘图工具)
加密的
解密的
不同的是 解密有一个验证
if($operation == 'DECODE') { if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 32) == md5($keyb.substr($result, 42))) { return substr($result, 42); } else { return ''; } } else { return $keyc.str_replace('=', '', base64_encode($result)); }
在前面我们已经知道这里的$result就是加密时的$string
$string是一个这样的字符串 “0000000000”.md5($keyb.$string).$string
所以第一个判断过了,第2个判断是要验证$result[10:42]==md5($keyb.$result[42:]) //分片写法
满足的话返回$string
好的 终于解释完代码了
我们想把密文传入cookie,解密后进行注入
我们不知道$secret_key,所以无法本地加密,而程序调用加密的地方只有一个
就是程序每次会回显的authcode(base64_encode($msg), ‘ENCODE’, $secret_key)
意思就是说 我们要通过这个密文 产生一个新的密文 这需要$cryptkey 这个加密秘钥
0x03 xor还原密钥
看看我们能得到什么
首先,我们很容易得到$cryptkey的前10位,这是因为$string的前10位是0
由此 我们也能得到$msg base64后的某10位,这是因为$cryptkey是循环使用的
得到的10位为 FnIGlzIGlu base64的原理我就不细讲了 去掉前俩位 解出明文 is in
目测是the flag is in the database
好像并不能得到什么了,无聊一直刷密文,再看看题目 xor挑战
应该是和xor有关
传入的$string不变 $key不变 那么产生的$string就是不变的
把密文去掉前8位,解码后xor 将得到俩个$cryptkey的xor 有戏啊
$cryptkey是sha256后的值 只有0~f 这16个字符
我们获取一张异或表看看 0~f xor 0~f alp = [0~f]
验证了一下,没有哪一行是完全相同的,那不是能求出每一位的值了
我们获取200条密文,把密文去掉前8位,解码后将第一条与后面的199条依次xor 将结果保存到数组num
然后遍历一遍xor表 如果能找到num0就将xor表这一行读入 看看能匹配几个保存下来
最后选取匹配最多的 那一行的值 就是sha256的值 语言总是苍白的 show code
for i in xrange(10,42): num = [] for n in xrange(1,len(dec_b64)): num += [ord(dec_b64[0][i])^ord(dec_b64[n][i])] most = [0,len(num)] tmp = 0 for j in xrange(len(xor_tab)): if xor_tab[j] == num[0]: right = len(num) for k in xrange(16): if xor_tab[k+(j/16)*16] in num: right -= 1 if most[0] < len(num) - right: if most[1] > right: most[0] = len(num) - right most[1] = right tmp = j/16 sha += hex(tmp)[2:] md5 += chr(ord(dec_b64[0][i])^ord(hex(tmp)[2:]))
就这样 我们得到了cryptkey 和md5($keyb.$string)
也可以得到$msg 为 R29vZCBqb2IhIFRoZSBmbGFnIGlzIGluIHRoZSBkYXRhYmFzZS4=
解码后是 Good job! The flag is in the database.
0x04 hash扩展绕过验证
得到了$cryptkey 我们已经能修改任意密文了
不过解密的时候验证$result[10:42]==md5($keyb.$result[42:]) 才能返回$string
$keyb是未知的,而md5($keyb.$result[42:])已经知道了 并且是$result[42:]可控的
这样 就可以通过hash扩展攻击产生一个新的string “0**0”.md5($keyb.$msg.$data).$msg.$data
网上已经有很多人分析过了 我就不再讲一遍了 附上一张图上的hash函数实现图
英文还行的看这里
没过4级的看这里
M是明文分组512位为一组 超过的448位的 需要填充一个新的组
CV0 是初始向量 产生的CV1 是M0的md5结果 嗯 就补充这俩点
注意这个脚本的CV位置 hash2个块后才覆盖产生的CV
细心的同学可能会发现我这个py是实时获取的
因为$secret_key 是他喵每小时会变的!!!
【via@90专栏 乐清小俊杰】