哈希碰撞攻击的核心是构造大量不同键名使其哈希值全部落入同一桶,使hashmap退化为单链表,导致操作时间复杂度从o(1)恶化至o(n),引发cpu满载与服务拒绝。

哈希碰撞攻击(HashDoS)的核心,是让 HashMap 的某个桶中塞入大量键名——这些键名彼此不同,但经过哈希计算后落入同一位置,迫使该桶的链表无限拉长。此时,HashMap 不再是 O(1) 的高效结构,而退化为单向链表,所有操作都变成线性扫描。
攻击如何落地:从键名构造到服务瘫痪
攻击者不需控制服务代码,只需向 Web 接口提交恶意参数即可。常见路径包括:
- HTTP 查询参数(如
?a=1&b=2&...),若后端用 HashMap 解析并缓存参数名 - POST 表单字段名、JSON 对象 key、Cookie 名称等,只要被映射进未加固的 HashMap 就可能中招
- PHP 中的
$_GET/$_POST、Java Spring 的@RequestParam Map、Node.js 的 querystring 解析等,历史上均曝出过此类漏洞
为什么特定键名能精准“打中一个桶”
关键在于哈希函数可预测。例如 Java 8 之前字符串的 hashCode() 是:s[0]×31^(n−1) + s[1]×31^(n−2) + ... + s[n−1]。
攻击者可逆向构造多组字符串,使其结果模数组长度后全等于同一个值(比如 0)。典型例子:
— "Aa" 和 "BB" 在某些哈希实现中就产生相同 hash;
— 更系统的方法是用算法批量生成“哈希种子族”,如针对 PHP 的 zend_inline_hash_func,几秒内就能产出上万个碰撞键名。
退化链表带来的真实性能断崖
当单桶链表长度达 10⁴ 级别时:
- 插入一个新参数需遍历整个链表比对 key.equals(),耗时从微秒级升至数十毫秒
- CPU 单核满载,线程卡在
HashMap.get()或put()内部循环中 - 一个请求就能拖慢整个线程池,多个并发攻击请求直接导致服务不可用
防御不是靠“不用 HashMap”,而是阻断退化链条
有效手段聚焦在两个层面:
- 运行时防护:启用 JDK 8+ 的树化机制(链表≥8 且数组≥64 时转红黑树),将最坏复杂度从 O(n) 压至 O(log n);但注意,这只能缓解,不能根除——log₂10000 ≈ 14 次比较仍远高于 O(1)
- 源头加固:对用户输入的键名做白名单过滤或长度/字符集限制;Web 层前置限流(如单 IP 每秒参数数≤50);禁用自动 map 绑定,改用显式字段解析










