CFB重放攻击

作者: 分类: CTF,Programming 时间: 2017-04-17 评论: 3条评论

http://cstc.xatu.cn/ - web300 - amdin
www.tar.gz

0x00 摸下套路

先把源码down下来,查看下大概逻辑如下:

1. index.php五个功能:home, register, login, manage, logout
2. encrypt.php实现RIJNDAEL_128(CFB)加解密
3. backup.txt存有flag.txt里存放的内容加密后的字符串
4. key.php里得到的identify和key我们没办法知道

刚看到的时候一下子想起了之前搞过的那个CBC字节反转攻击,但是细看后才发现并不是,CFB的,而且一些限制条件($md5),没办法简单的反转攻击。

看接下来实操部分之前,先了解清楚分组加密模式以及本次遇到的CFB的工作方式:分组密码工作模式 - 维基百科

0x01 原理分析

来看看这个生成cookie(tokensign)的的地方:

case 'login':
    if ($user) {
        header("HTTP/1.1 302 Found");
        header("Location: ?action=home");
    }elseif(isset($_POST['user']) && isset($_POST['pwd'])) {
        if ($_POST['user'] == '') echo 'Username Required';
        elseif ($_POST['pwd'] == '') echo 'Password Required';
        elseif (!login((string)$_POST['user'], (string)$_POST['pwd'])) echo 'Incorrect';
        else {
            $user = $_POST['user'];
            // get_indentify() 获取10位的key,做一个身份签名,防止身份伪造

            $md5 = md5(get_indentify().$user);
            $admin = 0;
            // $token = token_encrypt("$user|$admin|$md5");
            $token = token_encrypt("$user|$admin|$md5");
            setcookie('sign',$md5,time()+5*60,"/",'',false,true);
            setcookie('token',$token,time()+5*60,"/",'',false,true);
            header("HTTP/1.1 302 Found");
            header("Location: ?action=home");
        }
    }

我们可以看到,最终返回的cookietokensign,两个字段分别包含的内容是:

token = token_encrypt("$user|$admin|$md5");
sign = $md5

其中token包含的内容类似于这样一串去加密admim|0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,但是因为有padding(encrypt.phppad()函数实现),所以位数会扩展到16的倍数,记住这个,后面会用到。

现在来看看读取cookie后进行的操作:

$token = '';
$user = '';
$admin = 0;
if (isset($_COOKIE['token'])&&isset($_COOKIE['sign'])) {
    $sign = $_COOKIE['sign'];
    $token = $_COOKIE['token'];
    $arr = explode('|', token_decrypt($token));

    if (count($arr) == 3) {
        if (md5(get_indentify().$arr[0]) === $arr[2] && $sign === $arr[2]) {
            $user = $arr[0];
            $admin = (int)$arr[1];
        }
    }
}

token解密后,根据|来切割放进数组$arr中,判断数组长度是否是3,是的话就判断$arr[2]中的内容是否被篡改。最后会把$arr[1]中的值赋给$$admin,还是强制类型转换,这边就有问题了。

接下来我们来看看加密函数的具体内容,RIJNDAEL_128(AES) + CFB,有几个比较重要的参数:

Cfb_encryption.png

1. iv(初始向量)
2. Key(AES加密密钥)
3. Plaintext(明文)
4. Ciphertext(密文)

这边除了flag那一串不知道明文以外,其他明密文我们都知道,key对于我们来说没用,AES具体加密过程直接无视。IV的话我们来看看这边怎么产生的:

function getRandChar($length){
    $str = null;
    $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
    for($i=0;$i<$length;$i++){
        $n = rand(0, strlen($strPol) - 1);
        $str.=$strPol[$n];
    }
    return $str;
}

function token_encrypt($str) {
    $key = get_key();
    srand(time() / 300);
    $iv = getRandChar(16);
    return bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, pad($str), MCRYPT_MODE_CFB, $iv));
}

此处用time() / 300做随机数种子,rand()产生随机数,问题就在这里,在一分钟之内rand()产生的随机数序列是不会变化的,这个有点像数据库里rand(0),因此,时间戳60秒范围内都能拿到iv

到这里我们对整个流程都有个直观的认识了,我们的目的就是让我们是admin,进到index.php?action=manage中去解密backup.txt中的那串加密过的内容。

0x03 攻击过程

下面我就长话短说,有什么不懂可以留言。

因为iv和明密文分组都是16位一组,返回的token长度为:

len(token) == len($user|$admin|$md5) + len(padding)

我们这边注册的用户长度就为15iFuryStiFurySti,这样最后返回的token类似于下面这样(实际是加密过得,但是长度和对应位表示什么是一样的):

token = iFuryStiFurySti|0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e

当我们接受到token的时候,我们结合CFB重放攻击这个思想,扩展token的长度,变为如下:

fake_token = iFuryStiFurySti|0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0eiFuryStiFurySti|0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e

此时我们反转fake_token[16]使得其值从变为1。此时后面的数据被破坏了,现在大概是这种形式了:

fake_token = iFuryStiFurySti|1yyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0eiFuryStiFurySti|0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e

yyyyxxxx以及下面的zzzz是随机的数,这边形象表示。

可以看到,这边虽然有3个|,但是CFB模式解密是前面密文分组参与异或,也就是后面的iFuryStiFurySti|这16位一组的密文分组会被破坏,解密后相当于下面这个形式

fake_token = iFuryStiFurySti|1yyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0ezzzzzzzzzzzzzzzz0|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e

此时在两个|之间是1开头加上一对其他的可能可打印也可能不可打印的字符,这个被强制类型转换后只会得到1,所以这边其实可以类比为我们得到了这样一串:

~fake_token = iFuryStiFurySti|1|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e

那么这一串明显就能过第一个坎进到manage里去了。

接下来就是解密backup.txt里的这一串

34018770e87f5195923a434ce1a8bb9defe76053fff2ea04af6adb70e3f7d3792f22889951bec6dddf32cfaa7a33d4a3

我们还欠一个iv,之前说过sran()rand()的问题,这边我们只需要找到时间戳。通过查看backup.txt修改时间,可以得到时间戳

s14 ~ # date -d "2017-04-14 11:12:00" +%s
1492139520

timestamp.png

再丢到php里跑一下即可得到iv,此处需要注意的是php版本不一样得到的随机数序列是不一样的,一开始我是开了一台win03的测试,一直不对,怀疑人生,搞了3小时,最后灵光一闪想到了什么,开了一台linux就过了,很多人估计就卡在时间戳这里了,年度脑洞大赛..

getRandChar.png

接下来我们只需要解密就能得到想要的flag了..

flag.png

以下附上脚本:cfb_attack_exp.py

0x04 小结

花了半天撸这个题,主要是觉得挺有意思的。

by the way, 我就是那个墨麒麟安全实验的,实验室刚组建不久,站也还没全弄好,就先把文章丢自己这边了。另外有兴趣打打比赛,挖挖洞,日日站的小伙伴可以加入我们。

Just For Fun :)

标签: CFB

声明:文章基本原创,允许转载,但转载时必须以超链接的形式标明文章原始出处及作者信息。

已有 3 条评论

  1. dane
    dane

    大佬,我想加入你们,但很菜,求带

    时间: 2017-05-08 at 12:36 回复
    1. iFurySt

      可以加我qq或者微信,我们平时可以交流

      时间: 2017-05-13 at 00:09 回复
  2. Mxxy
    Mxxy

    先膜一波大佬..不过有个地方没怎么弄懂

    "可以看到,这边虽然有3个|,但是CFB模式解密是前面密文分组参与异或,也就是后面的iFuryStiFurySti|这16位一组的密文分组会被破坏,解密后相当于下面这个形式"

    为什么第二个iFuryStiFurySti|会被破坏呢。既然修改的是第一个iFuryStiFurySti|后面的那个1 也就是第二块开头 第二块的密文加密后应该是和第三块xor吧...然后第三块不应该是第一个md5的一部分么....果然我还是太菜了..还请大佬指教..qwq

    时间: 2017-12-29 at 21:46 回复

添加新评论