跳转至

实验原理

1.Hash 函数的一般结构和应用

Hash 函数被广泛应用于各种不同的安全应用和网络协议中,其中最常用的就是消息认证,本次实验就是关于 Hash 函数的消息认证存在的扩展长度攻击问题。另外 Hash 函数还被用在数字签名、产生单向口令文件、构建随机函数等。

Hash 函数 f 是一个压缩算法,它可以将不同长度的数据块任意消息,产生固定长度的 Hash 值。本次实验用到的Hash 函数是 SHA256 , 对于任意长度的消息, SHA256 都会产生一个256位的哈希值。

典型 Hash 函数采用的的是 MD(Merkle 和 Damgard 提出) 结构,该结构将输入消息分为 L 个固定长度的分组,每组长为 b 位,最后一个分组如果不足 b 位时需要将其填充为 b 位,如果分组恰好为 b 位,则需要再填充一个b 位的分组,最后一个分组的还需要填充消息的总长度。如下图所示。

图 2-1 迭代型 Hash 函数的一般结构

2.消息认证码 MAC

MAC 是用于验证信息的真实性。最简单的 MAC 算法是服务器将 key 和 message 连接到一起,然后用 Hash 算法比如 SHA256 获取摘要,然后进行验证。本次实验中我们要获取服务器的文件列表,除了要输入必要的用户名、用户 id 和命令外,还需要输入一个生成的 mac 信息摘要进行验证,验证通过后才能获取到文件列表。

我们访问服务器获取文件列表的URL是这样的:

http://www.seedlab-hashlen.com/?myname=JohnDoe&uid=1001&lstcmd=1
&mac=7d5f750f8b3203bd963d75217c980d139df5d0e50d19d6dfdb8a7de1f8520ce3

其中 mac 的值是通过如下命令生成的(key的值是客户端和服务器端已知的,且保密的): $ echo -n "123456:myname=JohnDoe&uid=1001&lstcmd=1" | sha256sum 7d5f750f8b3203bd963d75217c980d139df5d0e50d19d6dfdb8a7de1f8520ce3 -

当用户在浏览器上输入获取文件列表的命令信息时,服务器这段代码将会执行,只有 mac 匹配成功后才会正常返回文件列表,如下所示:

图 2-2 服务器代码

3.Hash 扩展长度攻击

前面提到,经典的 Hash 函数都是基于MD结构,这种结构在用于计算上面的 MAC 中都会存在一个漏洞,就是如果我们知道 message 和 MAC ,那么只需要知道 key 的长度而不需要知道 key 具体的值,就能够通过在 message 的后面添加一些填充的信息而计算出相应的 MAC,从而通过验证获取到想要的信息。 本实验简化了关于怎么求出关于 key 的长度的步骤(通常通过枚举长度,根据错误返回值猜测的 key 的长度),直接以 key 的长度为已知条件。

说明 ✨

关于 Hash 扩展攻击的具体实例,大家可以参考:记DedeCMS一处由哈希长度拓展攻击引起的越权漏洞https://www.anquanke.com/post/id/152589

3.1 填充

消息填充是 Hash 长度扩展攻击的关键,下面以 SHA256 为例进行说明。

在 SHA256 算法中,首先要对消息进行分组,每组长度为 512 ,最后一组的数据无论长度多少都要进行填充。

填充的方式如下:首先需要对信息进行填充,使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。然后在最后的64为填充为消息的真正长度。

填充的内容为:在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后,在这个结果后面附加一个以64位二进制表示的填充前信息长度。要求填充后消息+加上末尾64位的消息长度必须为512的整数倍。

3.2 SHA256 算法机制

SHA256 算法是将任意长度的消息,产生一个256位的 Hash 值。 SHA256 算法的具体实现步骤跟其他 Hash 算法一样,主要分为预处理和压缩两个步骤。

3.2.1 消息预处理

补位:对消息进行填充,填充的方式为 3.1 填充 部分的内容。填充后最终的长度是 512 的整数倍。 分块:将填充后完整的消息分块,每块为512位。

图 2-3 预处理

示例,假如消息为 “abc” ,那么首先将消息转换为二进制,此时消息长度为24位(01100001 01100010 01100011),然后补充 512-64(消息长度填充位数)-24(消息长度) = 424 位 1 开头其余全 0 的信息,最后64 位填充消息的长度。 填充后完整的消息如下:

图 2-4 消息“abc”填充

3.2.2 SHA256 算法实现机制

SHA256 的详细算法实现不是我们关注的重点,我们只关注其实现的机制,也就是 MD 构造,这种构造方式是每个消息块都会和一个输入向量做运算,第一个输入向量是初始化的,后面每个输入向量都是前面一个消息块的输出结果。如下图所示,CVq 是上一个消息块运算的结果, 同时也是下一块消息的输入向量。

图 2-5 SHA256算法机制

说明 ✨

SHA256 的详细算法实现我们本次实验不重点关注,感兴趣的同学可以参考如下链接:https://blog.csdn.net/wowotuo/article/details/78907380https://github.com/in3rsha/sha256-animation 也可以到B站上参考相关视频介绍。也可以参考我们理论课上关于 SHA512 算法的详细介绍。 工作原理都是类似的。

3.3 理解 SHA256 长度扩展攻击

根据前面的填充机制和 MD 的构造方式,如果一个字符串有两个字符串组成,比如 str = a + b, 如果第一个字符串 a 不知道具体信息只知道长度,而第二个字符串 b 是可控的。 但是我们知道第一个字符串的长度和 Hash 值,那么我们就可以根据填充规则精心构造下第二个字符串,从而算出 str 的 Hash 值。

下面以第一个字符串为 “abc” 为例,来看看如何进行攻击。

第一步:字符串 “abc” 的 SHA256 结果如下: echo -n "abc" | sha256sum ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

第二步:构造第二个字符串,其中一部分为 字符串 “abc” 填充为完整的 512 位的内容,如下所示。前面三个为 a, b, c, 三个字符,后面两段则为构造的第二个字符串的部分,其中中间一段为填充消息(1个1加423个0),最后一段为64位的消息长度。

01100001  01100010  01100011  10000000……00  00…011000

第三步:将上面填充的再加上一个字符串 cba, 转换为二进制后如下所示

01100001  01100010  01100011  1000……00  00…011000  01100011  01100010  01100001

第四步:因为字符串总长度大于512位,因此又要进行填充。填充后的信息如下所示,倒数第二段是填充的消息(1个1加423个0),最后一段是消息长度(在经过我们前两步扩展后消息长度为536=512+24)

01100001  01100010  01100011  10000000……00  00…011000  01100011  01100010  01100001 1000……00 00…1000011000

第五步:计算上面 abc + padding + cba 的 HASH 值

84c2d2720ac4a61e741e74d2f1712fb500f531c42f0e0b6b0633dda046438c76

回顾上面的步骤,程序在计算时,要经过下面几步:

  1. 由于填充后大于512位,先补全为1024位
  2. 将其分为两部分
  3. 计算第一部分的H0-H7的值
  4. 再用第一部分算出来的H0-H7拿来算第二部分的值。

我们知道的条件有三个如下:

  1. 第一个字符串 abc 的 SHA256 (ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad)
  2. 第一个字符串的长度=3
  3. 第二个字符串我们可以任意控制

因为我们知道第一步计算得出的 HASH 值,因此我们只需要把 SHA256的初始值换成第一步得出的 HASH 值,然后直接对第2个我们构造的字符串进行处理就可以得到最后的 HASH 值,其结果跟知道第一个字符串的内容是一样的。

4.抵御 HASH 长度扩展攻击

存在 HASH 长度攻击的算法主要有 MD5 SHA1 SHA2 等。防范此类攻击的方法可参考如下三种,本次使用我们使用的是采用 HMAC 算法来规避此类问题。 1. HASH(key, HASH(message)),将消息hash后再与key连接起来求 HASH 值 2. HMAC算法 HMAC(key||padding) = HASH(secret||HASH(secret||padding)), 使用 HMAC 算法,规避手动填充的问题。 3. 交换key和padding的位置