很难找到一个密码学的实例,就拿上一期的xor挑战来讲咯
和基友@overlord一起搞了俩天才弄出来,星期四下午刚学的知识,当天晚上就用上了,也是很神奇~
先上代码吧
<?php
include("config.php");
header("Content-type: text/html; charset=utf-8");
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 8;
$key = md5($key ? $key : '');
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = hash('sha256', $keya.md5($keya.$keyc));
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).md5($keyb.$string).$string;
$string_length = strlen($string);
$result = '';
for ($i=0; $i<$string_length; $i++){
$result .= $string[$i] ^ $cryptkey[$i % 64];
}
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));
}
}
if (isset($_GET['showSource'])){
show_source(__FILE__);
die;
}
session_start();
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 "<div style=\"text-align:left\"><h4>Welcome ".$row['username'].". My lord!</h4></div><br/>";
}
}
}
}
if (!$_SESSION['uid']) {
echo "<div style=\"text-align:left\"><h4>Decrypt me!: ".authcode(base64_encode($msg), 'ENCODE', $secret_key)."</h4></div><br/>";
}else{
echo $msg;
}
?>
<!--<a href="/?showSource">view source</a>-->
0x01 构造cookie注入
先忽略加密的部分,看看后面
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有关
灵光一闪 不会是把密文xor吧?
马上再看看加密的图
传入的$string不变 $key不变 那么产生的$string就是不变的
把密文去掉前8位,解码后xor 将得到俩个$cryptkey的xor 有戏啊
$cryptkey是sha256后的值 只有0~f 这16个字符
我们获取一张异或表看看 0~f xor 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函数实现图
英文还行的看这里
M是明文分组512位为一组 超过的448位的 需要填充一个新的组
CV0 是初始向量 产生的CV1 是M0的md5结果 嗯 就补充这俩点
附上俩个脚本
#########################################
修改 L.N.牛说不能给脚本 大家自己high吧..
#########################################
注意这个脚本的CV位置 2个块后才覆盖产生的CV
细心的同学可能会发现我这个py是实时获取的
因为$secret_key 是他喵每小时会变的!!!
截图留念
我X,研读两天代码,佩服authcode作者的构思巧妙,使用动态的keyc构造的cryptkey异或加密字符串,防止cryptkey被利用,使用secret_key的构造的md5做hash验证,防止伪造,在我看来几乎是不可能破解的。没有secret_key,也没有原文字符串,根本无从下手,写了个脚本跑了1~5位的printable字符,无解,果断放弃。网上只有一篇关于利用加密解出cryptkey ,使用keyc碰撞利用产生了相同的cryptkey解密字原文。
时间: 2016-04-19 at 13:47 回复本来还想使用authcode作为自己的系统里的某些加密,没想到,作者利用一张异或表就破解了cryptkey解密了原文又利用hash扩展绕过验证,构造了合法的加密字符串,攻陷了系统,自此authcode彻底沦陷。
不得不前来跪拜,顺便问一下,楼主还收基友不
这是我见过的最长的评论了 联系方式已经发在你的博客
时间: 2016-04-19 at 17:14 回复又看了下原版的authcode,对比了一下
时间: 2016-04-20 at 11:23 回复原版将cryptkey转为一个0~255的密钥簿,应该阻止了异或表进行攻击
将$keyb放在$string后面在进行md5防止扩展攻击
可见原版的authcode还是有较高的安全性的
已经准备好肥皂~
时间: 2016-04-21 at 17:52 回复