Hash Collision DoS

本文深入探讨了Hash Collision DoS攻击原理及其对多种编程语言的影响,包括Java、PHP和Node.js等,并提供了防御措施。

Hash Collision DoS事件及影响 

Hash Collision DoS能让受攻击的服务器变得巨慢无比。

这不是因为服务器的编码原因或是疏忽造成的,而是程序语言自身的问题Hash Collision DoS利用了各语言Hash算法的非随机性可以制造出N多不一样value,但是key一样数据,然后让Hash表成为一张单向链表,而导致整个网站或是程序的运行性能以级数下降。有数据说,10kb的数据量就会导致一个i7CPU马上占用率飙升100%,这真是恐怖。不幸的是,除了Perl之外,这个漏洞使得包括JavaJRubyPHPPython在内的以下各种开发语言和许多常用软件都纷纷中招:

  • 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补丁

事实上,Hash Collision DoS 漏洞并不是突然出现,早在2003年的一篇论文《通过算法复杂性进行拒绝式服务攻击就有相关报告进行了预警,但好像并没有引起当时正蓬勃发展的Java和其它开发语言的注意。以上不幸中招开发语言的列表还在继续更新之中,最新情况可查看: oCERT2011-003报告

更详细的内容,可参考这里。

原理及解决方法:Java相关

这些语言使用的Hash算法都是非随机的比如JavaOracle使用的Hash函数:

staticinthash(inth)

{

h ^= (h >>> 20) ^ (h >>> 12);

returnh ^ (h >>> 7) ^ (h >>> 4);

}

所谓非随机的” Hash算法,就可以猜。比如:

1)在Java里, AaBB这两个字符串的hash code(hash key) 是一样的,也就是Collision 

2)于是,可以通过这两个种子生成更多的拥有同一个hash key的字符串。如:”AaAa”, “AaBB”, “BBAa”, “BBBB”。这是第一次迭代。其实就是一个排列组合,写个程序就搞定了。

3)然后,可以用这4个长度的字符串,构造8个长度的字符串,如下所示:

"AaAaAaAa", "AaAaBBBB", "AaAaAaBB", "AaAaBBAa", 

"BBBBAaAa", "BBBBBBBB", "BBBBAaBB", "BBBBBBAa", 

"AaBBAaAa", "AaBBBBBB", "AaBBAaBB", "AaBBBBAa", 

"BBAaAaAa", "BBAaBBBB", "BBAaAaBB", "BBAaBBAa",

4)同理,就可以生成16个长度的以及256个长度的字符串,总之,很容易生成N多的这样的值。

在攻击时,只需要把这些数据做成一个HTTP POST 表单,然后写一个无限循环的程序,不停地提交这个表单。用浏览器就可以实现。当然,如果做得更精妙一点的话,把这个表单做成一个跨站脚本,然后找一些网站的跨站漏洞,放上去,于是能过SNS的力量就可以找到N多个用户从不同的IP来攻击某服务器。

要防守这样的攻击,有下面几招:

打补丁,把hash算法改了。

限制POST的参数个数,限制POST的请求长度。

最好还有防火墙检测异常的请求。

不过,对于更底层的或是其它形式的攻击,可能就有点麻烦了。

原理及解决方法:PHP相关

下面再结合PHP内核源码,聊一聊这种攻击的原理及实现。

PHP是使用单链表存储碰撞的数据,因此实际上PHP哈希表的平均查找复杂度为O(L),其中L为桶链表的平均长度;而最坏复杂度为O(N),此时所有数据全部碰撞,哈希表退化成单链表。下图PHP中正常哈希表和退化哈希表的示意图。

哈希表碰撞攻击就是通过精心构造数据,使得所有数据全部碰撞,人为将哈希表变成一个退化的单链表,此时哈希表各种操作的时间均提升了一个数量级,因此会消耗大量CPU资源,导致系统无法快速响应请求,从而达到拒绝服务攻击(DoS)的目的。 

攻击者可以通过一些方法间接构造哈希表来进行攻击。例如PHP可利用POST方式进行攻击,针对这种方式的哈希碰撞攻击,目前PHP的防护措施是控制POST数据的数量。另外的防护方法是在Web服务器层面进行处理,例如限制http请求body的大小和参数的数量等,这个是现在用的最多的临时处理方案。这些方法只是限制POST数据的数量,而不能彻底解决这个问题。彻底的解决方案要从Zend底层HashTable的实现动手。

一般来说有两种方式,

一是限制每个桶链表的最长长度;

二是使用其它数据结构如红黑树取代链表来保存碰撞了数据。

(并不解决哈希碰撞,只是减轻攻击影响,将N个数据的操作时间从O(N^2)降至O(NlogN),代价是普通情况下接近O(1)的操作均变为O(logN))。


目前使用最多的仍然是POST数据攻击,因此建议生产环境的PHP均进行升级或打补丁

至于从数据结构层面修复这个问题,目前还没有任何方面的消息。

更多详细内容可参考原文

原理及解决方法:Nodejs相关

以 connect 为示例说明Nodejs防御此问题

使用 connect.limit 限制 request-body-size,直接上 connect.limit 模块解决:

connect()

  .use(connect.limit('1mb'))

  .use(handleRequest)

修改 qs 模块,让其支持 keys-limit 和 allow-keys

querystring.js

PS: 提了pull request,但是估计在没有真实攻击示例放出来之前,是不会被接受的。

/**

 * Parse the given str.

 */

function parseString(str, options) {

  var limit = options && options.limit;

  var keys = options && options.keys;

  if (keys && Array.isArray(keys)) {

    keys = {};

    for (var i = 0, l = options.keys.length; i < l; i++) {

      keys[options.keys[i]] = 1;

    }

  }

  return String(str)

    .split('&', limit)

    .reduce(function(ret, pair){

      try{

        pair = decodeURIComponent(pair.replace(/\+/g, ' '));

      } catch(e) {

        // ignore

      }

      var eql = pair.indexOf('=')

        , brace = lastBraceInKey(pair)

        , key = pair.substr(0, brace || eql);

      if (keys && !keys[key]) {

        return ret;

      }

      var val = pair.substr(brace || eql, pair.length)

      val = val.substr(val.indexOf('=') + 1, val.length);

      // ?foo

      if ('' == key) key = pair, val = '';

      return merge(ret, key, val);

    }, { base: {} }).base;

}

/**

 * Parse the given query `str` or `obj`, returning an object.

 *

 * Options: (only effect on parse string)

 *

 *  - `limit` parse string split limit.

 *  - `keys`  which keys need to be parse.

 * @param {String} str | {Object} obj

 * @param {Object} options

 * @return {Object}

 * @api public

 */

exports.parse = function(str, options) {

  if (null == str || '' == str) return {};

  return 'object' == typeof str

    ? parseObject(str)

    : parseString(str, options);

};

还需要让 connect.query 模块 传递options参数给 qs.parse()

module.exports = function query(options){

  return function query(req, res, next){

    req.query = ~req.url.indexOf('?')

      ? qs.parse(parse(req.url).query, options)

      : {};

    next();

  };

};

同样 connect.urlencoded 模块也需要将options参数传递给 qs.parse()

    req.on('end', function(){

      try {

        req.body = buf.length

          ? qs.parse(buf, options)

          : {};

        next();

      } catch (err){

        next(err);

      }

    });

全部组合起来

var qsOptions = { limit: 100 };

connect()

  .use(connect.limit('1mb'))

  .use(connect.query(qsOptions))

  .use(connect.bodyParser(qsOptions))

  .use(handleRequest)

防范 http header 攻击

请求的 http header 也会导致hash冲突,在V8层面未修复hash算法之前,可以通过简单的 http_patch.js 修复此问题:

var http = require('http');

var IncomingMessage = http.IncomingMessage;

var _addHeaderLine = IncomingMessage.prototype._addHeaderLine;

// limit http header number

IncomingMessage.prototype._addHeaderLine = function(field, val) {

  if (!this.__headerCount__) {

    this.__headerCount__ = 0;

  } else if (this.__headerCount__ >= 100) {

    return;

  }

  _addHeaderLine.apply(this, arguments);

  this.__headerCount__++;

};

业界人士看法

@Laruence今天尝试了随机增长Hashtable大小来克服Hash Ddos, 不过后来证明这种方法只能防止Number Key, 而对于String Key, 

攻击者总能找到一些特殊的Key, 他们在DJB Hash以后的结果相同

目前新的修复方案进度搁置. 5.3.9可能只能发布包含限制max_input_vars的修复措施.

@beyondme37 问题是出在计算keyhash函数。比如我们的存入一个哈希表的数据为整数,我们计算keyhash函数如下

int hash(int x)

{

return (x % 5);

}

那我x输入6, 11, 16, 21, 26 … 返回的计算出的key都是1,都会存在哈希表1位置,即1位置冲突,而一般解决哈希表冲突的方法为拉链法,即数据全部存在1位置成单链表了,所以查找速度会下降为O(n)级别。 

@wenbo 可以简单理解为,tomcat 之类给你做了一个基础框架,这个框架会事先把提交的请求解析并装载在某个hash_map——那些会冲突的KEY可以理解为参数名,KEY=后面的值则是该参数的取值。然后,你的脚本不再需要解析http请求,而是直接从容器中用参数名取出各个参数的实际值即可。这个攻击针对的是第一步,即底层架构预先解析请求并将解析出的内容存储在某个容器内这个步骤。底层架构并不知道上层应用要如何解释这些参数,所以它只能把所有参数缓存起来等待处理;至于上层应用,此时并未被唤起,所以也无法对此决策。至多可以在部署时通过配置声明在我的所有网页里,最多会用到1000个参数,或者在我的应用里,参数及其内容加起来最多10K字节

目前暂无完美解决之道

虽然半个多月前Tomcat就紧急发布安全漏洞通知,同时微软也发布了相应的安全漏洞通知,但他们都是通过变通的方式来解决此拒绝服务漏洞就是告知大家,将请求参数据缓存值设小,也就是说,一次性的请求量不会导致CPU被全部占满——这真是无奈之举。除此之外,就是设计好HASH算法,一定要保证必然碰撞事件的概率降低,也就是说提高生产位数,带来的痛苦就是请求效率降低。防御之法是做好异常检测,理性区分正常与恶意的数据请求

资料参考:

Hash Collision DoS 问题

浅析 HashTable 碰撞拒绝服务漏洞 

Defense hash algorithm collision 防御hash算法冲突导致拒绝服务器

PHP哈希表碰撞攻击原理

Multiple programming languages vulnerable to DoS via hash algorithm collision

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值