代码审计学习(四) 三个白帽xor挑战

作者: 分类: 代码审计 时间: 2016-04-18 浏览: 5606 评论: 4条评论

很难找到一个密码学的实例,就拿上一期的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注入

先忽略加密的部分,看看后面

cookie.png

user和pass是解密出来后用\t来分割得到的,然后进入sql查询
查到的md5和输入的对比,过了以后输出用户名和msg

很简单的注入 主要解密出来的是这样的字符串就可以了 最后这个\t可以不加

' union select 1,2,'eccbc87e4b5ce2fe28308fd9f2a7baf3'#\t3\t

来看看效果

cookieyanzheng.png

这样就可以通过验证了

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 这一点非常重要

可能看文字 有同学已经懵逼了 我们来画张图(请原谅我不会用绘图工具)
加密的

入.png

解密的

出.png

不同的是 解密有一个验证

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吧?
马上再看看加密的图

入.png

传入的$string不变 $key不变 那么产生的$string就是不变的
把密文去掉前8位,解码后xor 将得到俩个$cryptkey的xor 有戏啊
$cryptkey是sha256后的值 只有0~f 这16个字符
我们获取一张异或表看看 0~f xor 0~f

xortab.png

验证了一下,没有哪一行是完全相同的,那不是能求出每一位的值了
我们获取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级的看这里

2%BYAXLRU1U56$U7GS6AGKI.png

M是明文分组512位为一组 超过的448位的 需要填充一个新的组
CV0 是初始向量 产生的CV1 是M0的md5结果 嗯 就补充这俩点

附上俩个脚本
#########################################
修改 L.N.牛说不能给脚本 大家自己high吧..
#########################################

注意这个脚本的CV位置 2个块后才覆盖产生的CV

细心的同学可能会发现我这个py是实时获取的
因为$secret_key 是他喵每小时会变的!!!

截图留念

QQ截图20160418230044.png

标签: none

订阅本站(RSS)

已有 4 条评论

  1. 我X,研读两天代码,佩服authcode作者的构思巧妙,使用动态的keyc构造的cryptkey异或加密字符串,防止cryptkey被利用,使用secret_key的构造的md5做hash验证,防止伪造,在我看来几乎是不可能破解的。没有secret_key,也没有原文字符串,根本无从下手,写了个脚本跑了1~5位的printable字符,无解,果断放弃。网上只有一篇关于利用加密解出cryptkey ,使用keyc碰撞利用产生了相同的cryptkey解密字原文。
    本来还想使用authcode作为自己的系统里的某些加密,没想到,作者利用一张异或表就破解了cryptkey解密了原文又利用hash扩展绕过验证,构造了合法的加密字符串,攻陷了系统,自此authcode彻底沦陷。
    不得不前来跪拜,顺便问一下,楼主还收基友不

    时间: 2016-04-19 at 13:47 回复
    1. 这是我见过的最长的评论了 联系方式已经发在你的博客

      时间: 2016-04-19 at 17:14 回复
  2. 又看了下原版的authcode,对比了一下
    原版将cryptkey转为一个0~255的密钥簿,应该阻止了异或表进行攻击
    将$keyb放在$string后面在进行md5防止扩展攻击
    可见原版的authcode还是有较高的安全性的

    时间: 2016-04-20 at 11:23 回复
  3. 已经准备好肥皂~

    时间: 2016-04-21 at 17:52 回复

添加新评论