我最近在 twitter 上发布了一个指向两个 PHP 脚本的链接,它们具有不同的行为,但具有相同的 MD5 哈希值。要验证这一点,请下载文件:
1 2 |
$ wget https://aqwu.net/md5collisions/a.php $ wget https://aqwu.net/md5collisions/b.php |
然后检查他们的 MD5 哈希值:
1 2 3 |
$ openssl md5 a.php b.php MD5(a.php)= 62e1d0d1620581693435aa75f5b6c964 MD5(b.php)= 62e1d0d1620581693435aa75f5b6c964 |
现在运行每个文件:
1 2 3 4 |
$ php a.php Behaviour A $ php b.php Behaviour B |
这些文件的行为不同,但使用 MD5 散列到相同的十六进制字符串。使用另一种哈希算法(在本例中为 SHA1)进行快速检查,确认这两个文件实际上是不同的:
1 2 3 |
$ openssl sha1 a.php b.php SHA1(a.php)= 6ef81f904b84d6ea53048004a26157816a85cfa3 SHA1(b.php)= d191a5eb7557231f461c09cb4dc565a23336778a |
那么这些文件是如何制作的呢?好吧,如果你看一下文件的源,你会发现这两个文件之间几乎没有区别。
cat a.php
1 2 3 4 5 6 7 8 9 10 11 |
<?php $space = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; if ('?G.???-)?????+?B?8????A???P?;"??P?ZU?5?$q4F?1c?-ŏC?S ?e?MIM???~?2?:?]b??\??/5???^?gn~3D7' == '?G.???-)?????+?B?8????A???P?;"??P?ZU?5?$q4F?1c?-ŏC?S ?e?MIM???~?2?:?]b??\??/5???^?gn~3D7' ) { echo 'Behaviour A',PHP_EOL; } else { echo 'Behaviour B',PHP_EOL; } |
$ cat b.php
1 2 3 4 5 6 7 8 9 10 11 |
<?php $space = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; if ('?G.???-)??????+?B?8????A???P?;"??Py[U?5?$q4F?1??-ŏC?S ?e?MIM???~?2?:?]b?p?\??/5???^?g?~3D7' == '?G.???-)?????+?B?8????A???P?;"??P?ZU?5?$q4F?1c?-ŏC?S ?e?MIM???~?2?:?]b??\??/5???^?gn~3D7' ) { echo 'Behaviour A',PHP_EOL; } else { echo 'Behaviour B',PHP_EOL; } |
发现有什么不同吗?它可能很难发现,但实际上在二进制数据的第一个块中存在细微的差异。
那么,所有这些二进制数据是怎么回事呢?二进制数据表示 MD5 算法中的冲突。在 Wang 于 2004 年首次发表的对 MD5 的攻击之后,为 MD5 创建碰撞非常容易。此外,您可以选择要创建的碰撞的起始值,这样它就不必位于文件的开头。 这是因为 MD5 与大多数哈希算法一样,一次对一个数据块(在 MD5s 情况下为 64 个字节)进行操作,然后使用此值作为下一个块的起点。接下来的两个块在两个文件之间有所不同,但是,它们是专门创建的,以便在插入到此位置时发生碰撞。两个文件中的所有后续块都相同。这是我用来生成文件的方法。
1) 首先,我创建了一个正好 64 字节长的文件,其中包含第一个单引号之前的所有内容。A 只是为了让它正好有 64 个字节长
1 2 3 4 |
<?php $space = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; if (‘ |
2) 通过 MD5 算法运行此内容,但略微修改的版本不会在消息中添加长度或任何填充,我在 PHP 中使用了我自己的 MD5 实现,并注释掉了附加填充的部分。您可以在此处获取此代码 https://gist.github.com/natmchugh/fbea8efeced195a2acf2. 生成哈希值 32f6df28e4a110970fb7f9938bee1686。
3) 使用此哈希值作为初始值来查找 MD5 冲突。我发现的最快的碰撞脚本是 Marc Stevens fastcoll, https://marc-stevens.nl/research/ 如果在 MD5 中创建冲突听起来需要很长时间 – 但事实并非如此。它需要多少时间,但在本例中,大约需要 15 秒才能生成 2 条消息,这些消息与初始值 32f6df28e4a110970fb7f9938bee1686 发生冲突。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ time ./fastcoll -i 32f6df28e4a110970fb7f9938bee1686 -o a b MD5 collision generator v1.5 by Marc Stevens (http://www.win.tue.nl/hashclash/) Using output filenames: 'a' and 'b' Using initial value: 32f6df28e4a110970fb7f9938bee1686 Generating first block: ..................................... Generating second block: S10............................................. Running time: 14.9727 s real 0m14.981s user 0m14.969s sys 0m0.006s |
4)然后,我将生成的两条消息附加到1中创建的文件中,以提供两个文件。
5). 这两个文件现在应该具有相等的长度并且具有相同的 MD5,但它们不会作为 PHP 运行。所以我在两批二进制数据之间添加了语句。
‘ == ‘ ‘ == ‘
6)接下来,我将碰撞a附加到两个文件中。这将生成一个语句,该语句在文件 a 中为 true(因为 2 个二进制 blob 确实相同),但在文件 b 中为 false。
7) 我在两条消息中附加了相同的 PHP 以完成 if 语句并生成您看到的输出。因此,实际上我们有两个文件,其中前 64 个字节是相同的。接下来的 128 个字节是 MD5 中的冲突,然后是相同的所有其他文本。毫不奇怪,当通过整个 MD5 算法(包括填充)传递时,这会产生相同的 MD5 哈希值。
虽然这种攻击可能看起来毫无用处,因为文件充满了 gobbledygook 并且依赖于相当明显的 if 语句,但有一些方法可以创建具有相同 MD5 哈希值的文件,这些哈希值不依赖于开关,并且其余文件内容可以完全不同.