找到了这个系列的原始作者:
http://www.cnblogs.com/rush/archive/2012/02/05/2339037.html
最近网络安全成了一个焦点,除了国内明文密码的安全事件,还有一件事是影响比较大的——Hash Collision DoS(通过Hash碰撞进行的拒绝式服务攻击),
有恶意的人会通过这个安全漏洞让你的服务器运行巨慢无比,那他们是通过什么手段让服务器巨慢无比呢?我们如何防范DoS攻击呢?本文将给出详细的介绍。
这一篇跟Hash关系比较密切。
首先,发生哈希冲突时,我们可以使用冲突解决方法解决冲突,而主要的哈希冲突解决方法如下:
开放地址法:在原hash空间中找位置放。 再哈希法 链地址法:开启新的链表来存储新的内容。 建立一个公共溢出区 上面开放地址法和链地址法的区别,可以查看 http://blog.csdn.net/w_fenghui/article/details/2010387
注意:
.NET是使用第一种策略解决哈希冲突,它根据某种原则将碰撞数据定位到其他槽中。
而PHP是使用单链表存储碰撞的数据,因此实际上PHP哈希表的平均查找复杂度为O(L),其中L为桶链表的平均长度;而最坏复杂度为O(N),此时所有数据全部碰撞,哈希表退化成单链表。下图是PHP中正常哈希表和退化哈希表的示意图。
现在主流编程语言都采用的哈希算法是DJB(DJBX33A),而.NET中的NameValueCollection.GetHashCode()方法就是使用DJB算法。 DJB的算法实现核心是通过给哈希值(Key)乘以33(即左移5位再加上哈希值)计算哈希值,接下来让我们看一下DJB算法的实现吧!
uint hash = 5381;
for (int i = 0; i < value.Length; i++)
{
// The value of ((hash << 5) + hash) the same as
// the value of hash * 33.
hash = ((hash << 5) + hash) + value[i];
}
针对哈希的攻击:
通过前面介绍.NET中GetHashCode()方法,现在我们对于其中的实现算法有了初步的了解,
由于哈希冲突的原理就是针对具体的哈希算法来构造数据,使得所有数据都发生碰撞。
这里我们使用了一个简单方法构造冲突数据——蛮力法。(效率低)
由于蛮力法效率低,所以我们采用更加高效的方法中途相遇攻击(meet-in-the-middle attack)或等效子串(equivalent substrings)来构造冲突数据。
等效子串:
当两个字符串的哈希值发生冲突,例如:hash(“string1”)=hash(“string2”),那么由这两个子串在同一位置上构成的字符串也发生哈希冲突, 假设“EZ”和“FY”在哈希函数中发生冲突,那么字符串“EzEz”,“EzFY”,“FYEz”,“FYFY”两两之间也发生冲突。 示例:
http://koto.github.io/blog-kotowicz-net-examples/hashcollision/kill.html
中途相遇攻击:
分成前后两节,先指定前面,和最终hash,然后构造后面的。 其实没怎么看懂。文中有。
总结:
发post请求时候,发出大量重复hash的post参数,来让对方的hash算法总是冲突,然后崩溃。 以上是我的理解。
关于hash碰撞为什么能够产生DoS攻击,可以结合下面这篇来看:
http://blog.jobbole.com/11516/
哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,
还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希表结构存储)。 PHP哈希表最小容量是8(2^3),最大容量是0×80000000(2^31),并向2的整数次幂圆整(即长度会自动扩展为2的整数次幂,如13个元素的哈希表长度为16;
100个元素的哈希表长度为128)。nTableMask被初始化为哈希表长度(圆整后)减1。 HashTable中的nTableMask是一个掩码,一般被设为nTableSize – 1,与哈希算法有密切关系,后面讨论哈希算法时会详述。
Zend HashTable的哈希算法异常简单:hashKey = key & nTableMask; 如果key是字符串,就先用time33算法,变成整型,然后处理。(time33算法前面也介绍了)
基本攻击
上文提到Zend HashTable的长度nTableSize会被圆整为2的整数次幂,假设我们构造一个2^16的哈希表,
则nTableSize的二进制表示为:1 0000 0000 0000 0000,而nTableMask = nTableSize – 1为:0 1111 1111 1111 1111。
接下来,可以以0为初始值,以2^16为步长,制造足够多的数据,可以得到如下推测: 0000 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0 0001 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0 0010 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0 0011 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0 0100 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0
概况来说只要保证后16位均为0,则与掩码位于后得到的哈希值全部碰撞在位置0。
攻击代码示例:
<?php $size = pow(2, 16); $startTime = microtime(true); $array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
$array[$key] = 0;
} $endTime = microtime(true); echo $endTime - $startTime, ' seconds', "\n";
作者说用了近88秒才完成,并且在此期间CPU资源几乎被用尽。
而正常的hash插入,如下,仅仅需要0.036秒。
<?php $size = pow(2, 16); $startTime = microtime(true); $array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $size; $key += 1) {
$array[$key] = 0;
} $endTime = microtime(true); echo $endTime - $startTime, ' seconds', "\n";
POST攻击
一般情况下很难遇到攻击者可以直接修改PHP代码的情况,但是攻击者仍可以通过一些方法间接构造哈希表来进行攻击。例如PHP会将接收到的HTTP POST请求中的数据构造为$_POST,而这是一个Array,内部就是通过Zend HashTable表示,因此攻击者只要构造一个含有大量碰撞key的post请求,就可以达到攻击的目的。具体做法不再演示。
防御方法
目前PHP的防护措施是控制POST数据的数量。在>=PHP5.3.9的版本中增加了一个配置项max_input_vars,用于标识一次http请求最大接收的参数个数,默认为1000。
另外的防护方法是在Web服务器层面进行处理,例如限制http请求body的大小和参数的数量等,这个是现在用的最多的临时处理方案。具体做法与不同Web服务器相关,不再详述。
上面的防护方法只是限制POST数据的数量,而不能彻底解决这个问题。例如,如果某个POST字段是一个json数据类型,会被PHPjson_decode,那么只要构造一个超大的json攻击数据照样可以达到攻击目的。
理论上,只要PHP代码中某处构造Array的数据依赖于外部输入,则都可能造成这个问题,因此彻底的解决方案要从Zend底层HashTable的实现动手。
一般来说有两种方式,一是限制每个桶链表的最长长度;二是使用其它数据结构如红黑树取代链表组织碰撞哈希(并不解决哈希碰撞,只是减轻攻击影响,
将N个数据的操作时间从O(N^2)降至O(NlogN),代价是普通情况下接近O(1)的操作均变为O(logN))。
目前使用最多的仍然是POST数据攻击,因此建议生产环境的PHP均进行升级或打补丁。至于从数据结构层面修复这个问题,目前还没有任何方面的消息。
其他各种语言针对此类哈希碰撞攻击有漏洞的情况:
除了Perl之外,这个漏洞使得包括Java, JRuby, PHP, Python在内的以下各种开发语言和许多常用软件都纷纷中招:
Java, 所有版本
JRuby <= 1.6.5 (目前fix在 1.6.5.1)
PHP <= 5.3.8, <= 5.4.0RC3 (目前fix在 5.3.9, 5.4.0RC4)
Python, all versions
Rubinius, all versions
Ruby <= 1.8.7-p356 (目前fix在 1.8.7-p357, 1.9.x)
Apache Geronimo, 所有版本
Apache Tomcat <= 5.5.34, <= 6.0.34, <= 7.0.22 (目前fix在 5.5.35, 6.0.35, 7.0.23)
Oracle Glassfish <= 3.1.1 (目前fix在mainline)
Jetty, 所有版本
Plone, 所有版本
Rack <= 1.3.5, <= 1.2.4, <= 1.1.2 (目前fix 在 1.4.0, 1.3.6, 1.2.5, 1.1.3)
V8 JavaScript Engine, 所有版本
ASP.NET 没有打MS11-100补丁
Java相关
这些语言使用的Hash算法都是“非随机的”,比如Java和Oracle使用的Hash函数:
static int hash(inth)
{
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
所谓“非随机的” Hash算法,就可以猜。比如:
1)在Java里, Aa和BB这两个字符串的hash code(或hash key) 是一样的,也就是Collision 。
2)于是,可以通过这两个种子生成更多的拥有同一个hash key的字符串。如:”AaAa”, “AaBB”, “BBAa”, “BBBB”。这是第一次迭代。
其实就是一个排列组合,写个程序就搞定了。
3)然后,可以用这4个长度的字符串,构造8个长度的字符串。 在攻击时,只需要把这些数据做成一个HTTP POST 表单,然后写一个无限循环的程序,不停地提交这个表单。用浏览器就可以实现。
当然,如果做得更精妙一点的话,把这个表单做成一个跨站脚本,然后找一些网站的跨站漏洞,放上去,于是能过SNS的力量就可以找到N多个用户从不同的IP来攻击某服务器。
要防守这样的攻击,有下面几招:
打补丁,把hash算法改了。
限制POST的参数个数,限制POST的请求长度。
最好还有防火墙检测异常的请求。
Nodejs也有类似问题:
使用 connect.limit 限制 request-body-size,直接上 connect.limit 模块解决。
防范 http header 攻击
请求的 http header 也会导致hash冲突,在V8层面未修复hash算法之前,可以通过简单的 http_patch.js 修复此问题:
if (this.__headerCount__ >= 100) {
return;
}
也有业内人士说:
目前暂无完美的解决方法。