您的位置:首页 > 其它

Hash Collision DoS

2017-08-19 17:39 239 查看
Hash Collision DoS事件及影响 

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

这不是因为服务器的编码原因或是疏忽造成的,而是程序语言自身的问题,Hash Collision DoS利用了各语言中Hash算法的“非随机性”可以制造出N多不一样的value,但是key一样数据,然后让Hash表成为一张单向链表,从而导致整个网站或是程序的运行性能以级数下降。有数据说,10kb的数据量就会导致一个i7的CPU马上占用率飙升100%,这真是恐怖。不幸的是,除了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补丁

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

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

原理及解决方法:Java相关

这些语言使用的Hash算法都是“非随机的”,比如Java和Oracle使用的Hash函数:

staticinthash(inth)

{

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

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

}

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

1)在Java里, Aa和BB这两个字符串的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 :问题是出在计算key的hash函数。比如我们的存入一个哈希表的数据为整数,我们计算key的hash函数如下:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: