Hash Collision DoS
1、描述
Hash Collision Dos攻击的原理很简单, 目前很多语言, 使用hash来存储k-v数据, 包括常用的来自用户的POST数据, 攻击者可以通过构造请求参数, POST大量的特殊的”k”值(根据每个语言的Hash算法不同而定制), 使得语言底层保存POST数据的Hash表因为”冲突”(碰撞)而退化成链表,使每次读取、插入操作都需要遍历链表,造成CPU使用率100% (来源:http://coolshell.cn/articles/6424.html)
这样一来, 如果每次请求的数据量足够大, 那么就可以使得语言在计算, 查找, 插入的时候, 造成大量的CPU占用, 从而实现拒绝服务攻击.
例如:
在resin4.0.7服务器上:
在Java HttpServletRequestImpl.getParameter() 是根据String类型的key来获取用户请求参数的值,所有的请求参数是放在java.util.HashMap中,HashMap是使用hash方法来决定key的分布,如果key.hashcode()一样,那么就会被放到同一个链表上,如下图:
而且因为entry数量不断增多,HashMap一直申请新的Entry[]数组--同hashcode的链表头,也会因为大量的ref占用jvm heap.
HashMap的key分布方法:
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
1. String.hashcode()的算法是s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1],就是每个字符的ascii值以质数31作为进制计算后得到的结果就是,31进制;
2. 同时,有个成员变量hash保存了计算过的hashcode(), 第一次调用String.hashcode()后,计算出来的hashcode()就保存在hash成员变量中,以后就直接返回了---String是常量,String的hashcode()也永远不会变化,可以缓存在成员变量里面 3. String.hashcode()的算法已经明确了,那么就可以尝试构造k冲突。
测试环境:
cpu: 16个processor
测试代码:HashCollisionMapAttack.java(注意配置heapsize)
总共16次恶意提交: http://www.mytestserver.com:20080/addData.do,导致:
同时访问
http://www.mytestserver.com:20080/ 变慢。
2、解决方案
1、通过nginx/apache 限制http请求body的大小和参数的数量等,这个是现在用的最多的临时处理方案。如果服务器没有对请求的参数做限制,会受到这种攻击,如果服务器配置高,而且服务器节点多,应该在load average报警之后,继续工作一段时间。
2、升级到resin4.0.25 / resin3.1.x。 resin说明:http://forum.caucho.com/showthread.php?p=34786
3、升级到resin 4.0.26
resin服务使用4.0.26之后,客户端采用16线程访问服务,服务器java cpu(16 processor)占用15++ %CPU时间,大概5s种之后回复正常,cpu load average 最高值在6.+,大概1分钟之后为0.+,DoS客户端的请求大概在30s-60s之后得到回复,之前在4.0.7上的大量请求都没有得到回复。
几秒中之后(最高值)
通过测试观察resin4.0.26有较强的hash collision dos抵御能力。
但是:resin4.0.26的配置和resin4.0.7的配置文件差别较大,需要重新处理配置文件。
原理:
com.caucho.util.HashMapImpl 代替了以前使用的java.util.HashMap。重写了key的二次hash算法,每次put有可能重新分布key-vale对应的数组。数据结构和HashMap的 hashcode-list模式不同,采用的是hashcode-value模式,如果出现hashcode collision,当前hashcode会再次计算,避免o(n)次的查询操作。
推测:hashcode collision导致的再次计算和内部数组的扩容 导致%cpu暂时升高。
代码:这个Map实现类在普通情况下的查询效率不如java.util.HashMap
private V putImpl(K key, V value)
{
Object item = null;
int hash = key.hashCode() & this._mask;
int count = this._values.length;
for (; count > 0; count--) {
item = this._values[hash];
if (item == null) {
this._keys[hash] = key;
this._values[hash] = value;
this._size += 1;
return null;
}
if (this._keys[hash].equals(key)) {
this._values[hash] = value;
return item;
}
hash = hash + 1 & this._mask;
}
throw new IllegalStateException();
}
通过在resin.xml配置 form-parameter-max 参数来避免Hash Collision Dos
<web-app id="/" root-directory="webapps/microblog" redeploy-mode="manual">
<form-parameter-max>${form_parameter_max?:100}</form-parameter-max>
</web-app>