08月06, 2019

CTF-web 笔记 19

最近在刷 Jarvis OJ,有一道题是关于哈希长度扩展攻击的。之前没有接触过密码学相关的攻击手法,加上自己数学基础不好而且比较笨,看了大半天才看懂原理,写此篇以记之。


题目

http://web.jarvisoj.com:32778

扫目录扫到 index.php~,改名为 index.php.swp,执行 vim 的恢复命令vim -r index.php,获得源码:

<!DOCTYPE html>
<html>
<head>
<title>Web 350</title>
<style type="text/css">
    body {
        background:gray;
        text-align:center;
    }
</style>
</head>
<body>
    <?php 
        $auth = false;
        $role = "guest";
        $salt = 'XXX'; //不可见
        if (isset($_COOKIE["role"])) {
            $role = unserialize($_COOKIE["role"]);
            $hsh = $_COOKIE["hsh"];
            if ($role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))) {
                $auth = true;
            } else {
                $auth = false;
            }
        } else {
            $s = serialize($role);
            setcookie('role',$s);
            $hsh = md5($salt.strrev($s));
            setcookie('hsh',$hsh);
        }
        if ($auth) {
            echo "<h3>Welcome Admin. Your flag is XXX";
        } else {
            echo "<h3>Only Admin can see the flag!!</h3>";
        }
    ?>
</body>
</html>

关键逻辑:

if ($role==="admin" && $hsh === md5($salt.strrev($_COOKIE["role"]))) {
    $auth = true;
}

为了防止篡改 Cookie,用了加盐的 MD5 做哈希校验,盐值未知。

对于不了解 MD5 算法缺陷的人来说,比如我,这样做看上去并没有什么问题。

但是以这种方式校验哈希值,是存在被攻击的可能的。


利用条件

进行长度扩展攻击,需要知道:

盐的长度
md5(盐 + 原文)

在不知道具体盐值的情况下,可以预测出:

md5(盐 + 原文 + 填充 + 恶意扩充)

若不知道盐的长度,可以枚举长度,并且枚举的次数不多。


算法原理

在了解这种攻击原理之前,先要知道 MD5 算法的大致原理。

原文分组与填充

先把原文分组,每一组为 512 位,最后一组要留出 64 位用于储存原文的长度,所以最后一组的长度要填充至 448 位(512 - 64 = 448),故填充后的长度为n * 512 + 448

填充内容的第一位为1,其余为0(1000...000),填充至长度符合要求为止。

分块

每一组再分成 16 块,每一块为 32 位。

向量初始化

在进行运算前,要对运算必须的向量进行初始化,初始向量的值为:

A = 0x01234567
B = 0x89ABCDEF
C = 0xFEDCBA98
D = 0x76543210

算法流程

初始化完成后,开始进行哈希值的计算。

下图为算法的大致流程:

md5

先由初始向量与原文的第一组进行运算(具体运算方式不必知道)。

运算得到的 128 位的值将向量更新

更新后的向量,与下一组原文进行运算,并再对向量进行更新

当所有分组都运算完毕,此时的向量值就是 MD5 的计算结果


攻击原理

从上面的流程可以看出,前一组的运算结果,直接参与了下一组的运算

不难想到,若攻击者知道某一次 MD5 运算的结果,则其可以把该结果作为新的向量,并在之前原文的尾部恶意添加新的原文分组,再做若干次运算。

如此,即便不知道旧的原文,也能对恶意扩展后的原文的哈希值进行预测。


举个例子

假如我知道:

md5($salt . "human") = 3b5b5bd650281874e28e41a85ad3cd43
$salt 长度为 8 字节

我想在“human”后面加上“dog”。

但是加上“dog”之后哈希值变了,通不过验证,这时我可以使用下面步骤来通过验证。

人工扩充

由于盐值未知,使用 8 个 X 代替:

XXXXXXXXhuman

目前总长度为 13 字节(104 位),故填充至 448 位:

XXXXXXXXhuman\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00

注意,填充的第一个字节为\x80,是因为其对应的二进制为1000 0000,其余的\x00则对应0000 0000,满足填充要求。

由于长度为 104 位,十六进制表示为\x68,对应字符为h,故长度为:

h\x00\x00\x00\x00\x00\x00\x00

补上长度后:

XXXXXXXXhuman\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00

人工填充完毕。

覆盖向量

而我们所知的3b5b5bd650281874e28e41a85ad3cd43就是运算至此的向量。

先分段:

3b 5b 5b d6
50 28 18 74
e2 8e 41 a8
5a d3 cd 43

再逆序填入,获得向量值:

A=0xd65b5b3b
B=0x74182850
C=0xa8418ee2
D=0x43cdd35a

计算

使用上面得到的向量,对“dog”进行新一轮的计算,结果如下:

db81ccccf54f19c73b120d0bb3baa38a

这个结果正是如下字符串的哈希值:

XXXXXXXXhuman\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00dog

回到题目

此题比较特殊,没有给盐值的长度,并且涉及到一个反 PHP 序列化的机制。

先了解一下这个机制,比如下面代码:

<?php
    $s = 's:5:"admin";  s:5:"guest";';
    echo unserialize($s);
?>

输出的结果是"admin"。

又因为题目在计算哈希值之前,对序列化字符串进行了翻转。

所以在这题中,利用这个反序列化函数的特性,我们才能使用哈希长度扩展攻击。

用上面例子的方式造出特殊的字符串,字节翻转、编码之后,得到 Payload:

s:5:"admin"%3b%00%00%00%00%00%00%00%c0%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%80s:5:"guest"%3b

(这个是多次尝试后,盐值长度为 12 时,通过验证的 Payload)

使用工具 HashPump,计算出匹配的哈希值:

hashpump -s 3a4727d57463f122833d9e732f94e4e0 -d ';"tseug":5:s' -k 12 -a ';"nimda":5:s'
>>> fcdc3840332555511c4e4323f6decb07

改包,获得 flag:

flag

本文链接:https://blog.cindemor.com/post/ctf-web-19.html

-- EOF --