哈希长度扩展攻击

前言

讲一种密码学攻击方式——哈希长度扩展攻击,它是在web领域中与密码学相关的一类攻击方式。

科普

与本文正题没有太大关系,就当做科普吧。(凑字数)

哈希值

哈希值也称为散列值。哈希值的作用等同于现实生活中“指纹”的作用,每个人指纹是独一无二的,因此通过类比可知哈希值的作用就是对消息的取证。比如你如何判断你从非官方的地方下载的软件是否为盗版?很简单,官方网站在发布软件的同时会提供一个哈希值和计算该值所用的算法,你只需要拿你软件进行相同的算法,得到的哈希值与官方的标准哈希值比对是否相同即可。

哈希函数

哈希函数称为单向散列函数,就是用于生成哈希值的函数。哈希函数的算法包括MD4、MD5、SHA-1、SHA-2、SHA-3、SHA-256等。为了确保其生成的哈希值能够如同指纹般独一无二,哈希函数必须要求具有很强的抗碰撞性(包括强抗碰撞性和弱碰撞性)。

强抗碰撞性

如果能够找到两条不同的消息,使他们的哈希值是相同的,那么就说此哈希函数不具备强碰撞性。

弱抗碰撞性

已知一条确切的消息及其哈希值,如果能够找到另外一条具有相同哈希值的消息,那么就说此哈希函数不具备弱碰撞性。

哈希函数如果不具备强抗碰撞性,同时意味着它不具备弱碰撞性。

正题

现在正式开始讲哈希长度扩展攻击,首先了解导致存在这种攻击的原因。

Merkle–Damgård结构

Merkle–Damgård简称为MD结构,他是用于处理哈希函数加密对象是较长字符时的一种方案,下图是维基百科对该结构的展示图:

《Hash Length Padding Attack》

分块

对所要加密的消息(message)每个字符进行十六进制的转化,当消息长度较短时(小于或等于448bit),则直接写入消息块(message block)中。当消息长度大于448bit时,则要对消息进行分块处理,即每448bit分一块,也就是每56个字节就分一块。

补位(padding)

分块完成之后,往往最后一个分块的长度小于448bit,这个时候必须通过补位直到达到448bit。

补位的规则:

在补位的第一位的第一个字节的第一位写入1(也即10000000,转换为十六进制是\x80),然后后面都写入0。

添加有效信息长度

补位完成后,还要在每一个消息块中再添加8个字节(即64bit)用于标明该消息块有效信息的长度。

所以最终每一个消息块的长度都是:448bit+64bit=512bit

比如我们对abcd(4个字节)进行补位,那么按照补位的规则最终结果为:

\x61\x62\x63\x64\x80\x00\x00…\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00

十六进制\x61对应十进制97,也即是a的ASCII码

十六进制\x80对应二进制10000000

十六进制\x20对应十进制32,也即有abcd这4个字节

对消息块加密

首先有一个随机生成的初始化向量IV(initialization vector),他会与第一个消息块进行复杂的算法运算(我们不必理会是什么算法)得到一个新的值,然后再用这个新的值与第二个消息块去进行与第一次一样的算法运算再次得到一个新的值……如此重复下去直到最后一个代码块加密完成,即可得到hash值。

《Hash Length Padding Attack》

攻击原理

借助一道题来了解攻击原理:

关键代码

$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!
$username = $_POST["username"];
$password = $_POST["password"];
if (!empty($_COOKIE["getmein"])) {
      if (urldecode($username) === "admin" && urldecode($password) != "admin") {
          if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))){
                      echo "Congratulations! You are a registered user.\n";
                      die ("The flag is ". $flag);
                  }
          else {
             die ("Your cookies don't match up! STOP HACKING THIS SITE.");
            }
        }
    else {
        die ("You are not an admin! LEAVE.");
      }
}

setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

代码意思

给了一个提示:secret的长度是15个字节。

我们能够传三个变量:POST传值传username和password,Cookie传值传getmein

如果传的getmein为空,后台响应头会给一个Cookie,这个Cookie就是 md5($secret.”adminadmin”),也就是说我们已知了 md5($secret.”adminadmin”),这一点很重要,我们称后台的md5已知。

如果传的getmein不为空,

先通过判断:if (urldecode($username) === "admin" && urldecode($password) != "admin")

如果成立,再判断if($COOKIE["getmein"]==md5($secret.$username.$password))

如果成立就可以得到flag。

直接思路

如果我们传的getmein= md5($secret.”adminadmin”) ,username=admin,password=admin,就可以满足条件得到flag,但是前提又要求password不能为admin。似乎前后矛盾了。

另辟蹊跷

username是不用去改的,现在需要改变的就是password

还有一个已知的条件我们没有利用:secret的长度已知是15个字节

结合我们刚才讲的MD结构可知,如果被加密的字符串长度超过56个字节(448个bit),就会进行分块操作,所以如果我们故意让password变得很长,也就是让$secret.$username.$password的长度扩展,这样的话md5($secret.$username.$password))就需要进行分块操作,而分块意味着需要两次加密。

这个时候就需要根据补位规则来构造出password,使$secret.$username.$password为:

xxxxxxxxxxxxxxxadminadmin\x80\x00\x00\x00…\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00hed9eh0g

这样的话他们就会被分成两块:

第一块:xxxxxxxxxxxxxxxadminadmin\x80\x00\x00\x00…\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00

第二块:hed9eh0g

现在我们就是要算出getmein的值是多少,而getmein就是对第二块加密后的结果。

而第二块加密所需要的初始向量IV就是第一块的加密结果,它是已知的:md5($secret.”adminadmin”)

既然被加密的明文我们已知,初始向量也已知,那么我们就可以得到第二块的加密结果了。

扩展长度之前:

扩展长度后:

这意味着我们并不需要知道secret的值是什么,只需要知道screct的长度length是多少和md5($secret.”adminadmin”)

而关键就是在于password的值的构造,如果每次都要根据补位规则来构造password的值还是很麻烦的,而工具hashpump就很好的解决这个问题。

hashpump

《Hash Length Padding Attack》

选项含义:

signature: 已知消息在服务器端生成的hash(加密第一块消息块的哈希值)

data: 已知的消息 

key length:服务器端$secret的长度+$username的长度

data to add:想要附加的数据

因此最终的payload为:

username=admin

password=admin%80%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%c8%00%00%00%00%00%00%00hed9eh0g

getmein=3d0496d16729aa9b1eaa56be5383e112

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据