【转载】网络攻击技术(三)——Denial Of Service & 哈希相关 & PHP语言 & Java语言

时间:2021-08-24 20:05:01

找到了这个系列的原始作者:

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中正常哈希表和退化哈希表的示意图。

【转载】网络攻击技术(三)——Denial Of Service & 哈希相关 & PHP语言 & Java语言

现在主流编程语言都采用的哈希算法是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;
}

也有业内人士说:

目前暂无完美的解决方法。