
引言
MD5(Message Digest 5)曾是文件完整性校验的事实标准。它的 128 位哈希被广泛用于软件下载、文件上传系统等场景。然而,2004 年王小云教授展示了实用的碰撞攻击,彻底打破了 MD5 的安全性。时至今日,MD5 在密码学上已被攻破,但许多遗留系统仍依赖它进行文件验证。本文聚焦于一个特定的攻击向量:文件上传系统中的 MD5 碰撞。我们将解释攻击者如何上传一个与良性文件具有相同 MD5 哈希的恶意文件,从而绕过基于哈希的白名单或去重检查。你将了解底层机制、逐步利用示例、常见陷阱以及防御方法。
试试我们的 哈希文本工具 来观察微小的输入变化如何产生完全不同的哈希值。
MD5 碰撞的原理
Merkle-Damgård 结构
MD5 使用 Merkle-Damgård 构造:输入消息被填充到 512 位的整数倍,分成 512 位的数据块,并迭代处理。每个数据块通过一个 64 轮的压缩函数更新 128 位的内部状态(四个 32 位字:A、B、C、D)。
关键缺陷在于压缩函数:对于精心选择的两个数据块之间的差异,内部状态的差异在 64 轮后抵消,产生相同的最终哈希。这称为差分路径。
王小云的差分攻击
王小云的攻击找到两个不同的 512 位数据块,当它们被插入到消息的特定位置时,会导致整体 MD5 哈希碰撞。攻击复杂度约为 2^39 次操作——比生日攻击的 2^64 边界快数万亿倍。现代工具如 fastcoll 可以在普通 PC 上几秒钟内生成一对碰撞文件。
选择前缀碰撞
一种更强大的变体是选择前缀碰撞:给定两个不同的前缀(例如,一个良性文件和一个恶意负载),攻击者可以附加不同的后缀,使得完整消息具有相同的 MD5 哈希。这正是文件上传攻击所需要的。
为什么文件上传系统容易受到攻击
许多文件上传系统使用 MD5 哈希进行:
- 去重:只存储具有相同哈希的文件的一份副本。
- 完整性检查:验证上传的文件是否与已知的良好哈希匹配。
- 命名:将文件重命名为其 MD5 哈希以防止路径遍历。
如果攻击者能够上传一个与现有良性文件(例如系统配置文件或其他用户的文档)具有相同 MD5 的文件,他们就可以覆盖它或绕过安全检查。攻击过程如下:
- 攻击者获取一个系统接受的良性文件(例如
allowed.jpg)。 - 使用碰撞工具,攻击者生成第二个具有相同 MD5 哈希但内容不同的文件(
malicious.jpg,例如包含 PHP 代码或恶意软件)。 - 攻击者上传
malicious.jpg。系统计算其 MD5,发现与白名单哈希匹配,接受它——可能覆盖原始文件或执行负载。
实战示例:在文件上传中利用 MD5 碰撞
我们将使用 fastcoll(王小云攻击的一个流行实现)创建两个具有相同 MD5 哈希但内容不同的文件。然后模拟一个使用 MD5 进行去重的文件上传系统。
步骤 1:准备一个良性文件
创建一个简单的文本文件 benign.txt:
This file is safe.
步骤 2:生成碰撞文件
以 benign.txt 为前缀运行 fastcoll。它生成两个输出文件 coll1.bin 和 coll2.bin,它们共享相同的 MD5 哈希,但在一小块数据上不同。
fastcoll_v1.0.0.5.exe -p benign.txt -o coll1.bin coll2.bin
步骤 3:验证碰撞
检查两个文件的 MD5 哈希:
certutil -hashfile coll1.bin MD5
certutil -hashfile coll2.bin MD5
两者输出相同的 32 字符十六进制字符串,例如 5d41402abc4b2a76b9719d911017c592。
步骤 4:检查差异
使用十六进制编辑器(如 WinHex)比较两个文件。你会看到一个小区域(大约在字节 64–127)的比特不同。这就是碰撞块。
| 偏移 | coll1.bin | coll2.bin |
|---|---|---|
| 0x40 | 0x12 0x34 | 0x56 0x78 |
| ... | ... | ... |
步骤 5:模拟文件上传攻击
假设服务器按 MD5 哈希存储文件并进行去重:如果存在相同哈希的文件,则拒绝上传或覆盖旧文件。上传 coll1.bin 作为 photo.jpg。服务器计算其 MD5 并存储为 5d41402abc4b2a76b9719d911017c592.jpg。现在上传 coll2.bin 作为 photo.jpg。服务器计算相同的 MD5,发现已存在,并覆盖原始文件为恶意内容。之后下载 photo.jpg 的用户将收到恶意版本。
针对 MD5 碰撞的防御措施
| 防御措施 | 工作原理 | 有效性 |
|---|---|---|
| 使用 SHA-256 或更强 | 用 SHA-256、SHA-512 或 BLAKE2b 替换 MD5。 | 高——目前没有已知的实际碰撞。 |
| 内容类型验证 | 验证文件魔数,而不仅仅是扩展名。 | 中等——可能被多语言文件绕过。 |
| 沙盒执行 | 将文件存储在 web 根目录之外,通过脚本提供服务。 | 高——防止直接执行。 |
| 双重哈希(MD5+SHA-256) | 组合两个哈希;攻击者必须同时碰撞两者。 | 中等——增加复杂度但并非未来-proof。 |
| 随机化文件名 | 使用 UUID 或随机字符串,而不是哈希。 | 高——消除基于哈希的覆盖攻击。 |
常见陷阱
- 认为 MD5 在非安全场景中仍然安全:即使对于去重,攻击者也可以强制碰撞来破坏数据。
- 只检查文件扩展名:攻击者可以在有效的图像中嵌入可执行代码(多语言文件)。
- 按 MD5 哈希存储文件:使得基于哈希的覆盖攻击成为可能。
- 忽略选择前缀碰撞:即使你控制了文件的一部分,攻击者也可以附加碰撞块。
- 不更新遗留系统:许多企业系统仍然依赖 MD5 进行文件完整性检查。
常见问题
在非对抗场景中,MD5 还能用于文件完整性校验吗?
可以,如果你只需要检测意外损坏(例如网络传输错误),MD5 没问题。但如果存在恶意篡改的可能,请使用 SHA-256。
今天生成 MD5 碰撞有多快?
在现代 CPU 上,fastcoll 可以在不到一分钟内生成一对碰撞文件。使用 GPU 加速,只需几秒钟。
SHA-1 有同样的问题吗?
是的,SHA-1 也被攻破了(2017 年的 SHAttered 攻击)。然而,SHA-1 碰撞需要更多资源(约 11 万美元的云算力)。尽管如此,永远不要将 SHA-1 用于安全目的。
SHA-256 安全吗?
SHA-256 目前被认为是安全的。没有实际可行的碰撞攻击。它是所有安全应用的最低推荐标准。
我可以使用 MD5 进行密码哈希吗?
不行。MD5 太快且容易受到彩虹表攻击。请使用 bcrypt、Argon2 或 PBKDF2。
结论
MD5 碰撞对依赖哈希验证或去重的文件上传系统构成了真实威胁。借助 fastcoll 等工具,攻击者可以在几秒钟内生成碰撞文件。修复方法很简单:迁移到 SHA-256 或更强的算法,结合内容验证,并且永远不要单独信任哈希来保证安全。使用我们的 哈希文本工具 比较不同算法的哈希值,看看安全性保证的差异。