精华内容
下载资源
问答
  • 为了解决攻击点在能量迹中具体位置的识别问题,在对侧信道碰撞攻击技术研究的基础上,提出了通过计算能量迹中每个采样点的方差来识别攻击点的方差检查技术。
  • hash是密码学和平时的程序中经常会用到的一个功能,如果hash算法设计的不好,会产生hash碰撞,甚至产生碰撞攻击。 今天和大家详细探讨一下碰撞攻击

    简介

    hash是密码学和平时的程序中经常会用到的一个功能,如果hash算法设计的不好,会产生hash碰撞,甚至产生碰撞攻击。

    今天和大家详细探讨一下碰撞攻击。

    什么是碰撞攻击

    所谓碰撞攻击指的是对于同一个hash函数来说,两个不同的input通过hash计算得到了同样的hash值。用公式来说就是:

    hash(m1) = hash(m2)
    

    这个攻击有什么作用呢?

    举个例子,通常我们通过网络下载应用程序或者软件,除了下载链接之外,还会提供一个MD5的校验码。这个校验码就是用来校验下载的软件是不是官方提供的软件。

    MD5算法也是一种hash算法,如果恶意用户可以构造一个和原始软件一样MD5的软件的话,就很可能实施碰撞攻击。

    还有一种情况用在数字签名中。在数字签名中,因为效率的原因,如果文章特别大的情况下,通常会先取文章的hash值,然后对这个hash进行签名。

    所以这里面有两个可以被攻击的地方,一个就是hash碰撞,一个就是签名算法。

    举个例子,比如说师妃暄给徐子陵写了一封信A,说是凌晨的时候在竹林有事情相告,但是没有直接交给徐子陵而是给了他的好兄弟寇仲,寇仲考虑到夜晚太危险了,不想让他的好兄弟冒险,于是伪造了这封信A,构造了和原来的信A同样hash值的信B,并附带了师妃暄的签名。

    徐子陵收到了信B和签名,经过验证发现确实是师妃暄写的,于是就没有去赴约。

    碰撞攻击取决于hash算法的强度,像是MD5和SHA-1这些hash算法已经被证明是不安全的,可以在很快的时间内被攻破。

    选择前缀冲突攻击

    除了前面传统的碰撞攻击之外,还有一种叫做Chosen-prefix collision attack选择前缀冲突攻击。

    攻击者可以选择两个不同的前缀p1和p2,然后附在不同的字符串m1,m2前面,那么有:

     hash(p1 ∥ m1) = hash(p2 ∥ m2)    其中 ∥ 表示连接符
    

    我们看一个在SHA-1中由盖坦.勒伦(Gatan Leurent)和托马.佩林(Thomas Peyrin)发现的一个攻击的例子,这是两个分别带有前缀99040d047fe81780012000和99030d047fe81780011800的例子。

    两个消息内容可以从下面下载:

    messageA: sha-mbles.github.io/messageA

    messageB:sha-mbles.github.io/messageB

    我们可以看下消息的截图:

    这两个消息经过sha1sum运算,可以得到相同的hash值。

    sha1sum messageA : 8ac60ba76f1999a1ab70223f225aefdc78d4ddc0

    sha1sum messageB: 8ac60ba76f1999a1ab70223f225aefdc78d4ddc0

    java中的hash攻击

    java中有一个经常会用到的类叫做hashMap,在JDK7之前,HashMap在存储数据的时候如果遇到了hash冲突,则会将数据以链表的形式插入到这个hash节点的最后。

    这样会有什么缺点呢?

    那么就是如果有恶意攻击者,一直向hashMap中插入同样hash值的key对象,那么hashMap实际上就会退化成为一个链表。

    这样会大大影响hashMap的查询效率。如果数据特别大的话,可能就会导致DDOS攻击。

    这个问题的根本原因就是java中hashMap中的hash计算太过简单,很容易就能够找到相同hash值的key。

    实际上在2011年tomcat还发布了一个关于这个问题的漏洞解决方案。

    虽然这是java的问题,但是最后的锅还是由tomcat来背。tomcat的做法就是限制maxPostSize,从最大的20M改成了10K,这样可以有效的减少请求中的item大小。

    当然,在JDK8中,原来的链表结构已经被改成了红黑树结构,相信也是为了避免这种DDOS hash攻击的方案。

    原像攻击Preimage attack

    和碰撞攻击类似的还有一个攻击叫做原像攻击。

    原像攻击的抵御需要满足两个条件,第一个条件是给定一个hash值y,很难找到一个x,使得hash(x)=y。

    第二个条件就是给定一个x,很难找到一个y,使得hash(x) = hash(y)。

    很明显,碰撞攻击的抵御一定满足第二个条件,但是不一定满足第一个条件。

    本文已收录于 http://www.flydean.com/collision-attack/

    最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

    展开全文
  • 最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现,需要的朋友可以参考下
  • MD5算法原理及其碰撞攻击
  • OCB模式及其系列变体的碰撞攻击
  • RIPEMD-160的高效碰撞攻击框架
  • 对完整扩展MD4的碰撞攻击和对RIPEMD的伪原像攻击
  • 对谷物v1流密码的快速近碰撞攻击
  • 对40步RIPEMD-128的实际碰撞攻击
  • hash碰撞攻击就是构造恶意的数据是hash表退化为链表,每次插入数据都会遍历链表,消耗大量服务器资源,从而达到攻击目的。php的数组就是利用hash表实现的,对于碰撞的数据,php采用双向链表解决方案,所以可以利用...

    一、前言

    hash碰撞攻击就是构造恶意的数据是hash表退化为链表,每次插入数据都会遍历链表,消耗大量服务器资源,从而达到攻击目的。php的数组就是利用hash表实现的,对于碰撞的数据,php采用双向链表解决方案,所以可以利用PHP的数组进行hash碰撞攻击。

    二、模拟攻击

    很多接口都采用json数据格式来传输,通常php都会使用php:input获得数据流,然后使用json_decode解析json数据。例如:

    $json = file_get_contents('php://input');
    $arr = json_decode($json, true);
    var_dump($arr);
    

    这种很容易被人利用hash碰撞攻击,导致cpu资源被耗尽。

    下面我们来看看构造65536个元素的正常数组和恶意数组所需要的时间:
    创建一个test.php脚本,写入

    
    $size = pow(2, 16);
    $arr = [];
    $startTime = microtime(true);
    for($i=0,$k=0; $i<$size; $i++,$k+=$size) {
            $arr[$k] = 0;
    }
    $endTime = microtime(true);
    echo "恶意插入{$size}个元素耗时 ", $endTime - $startTime, " s\n";
    
    file_put_contents('./hash.json', json_encode($arr));
    
    $arr = [];
    $startTime = microtime(true);
    for($i=0; $i<$size; $i++) {
        $arr[$i] = 0;
    }
    $endTime = microtime(true);
    echo "正常插入{$size}个元素耗时 ", $endTime - $startTime, " s\n";
    
    

    先用php5.6运行此脚本:
    在这里插入图片描述
    由上图可以看到耗时相差巨大。

    下面再用php7运行此脚本:
    在这里插入图片描述

    由上图可以看出php7比php5.6耗时短的多,这是由于php7优化了hash表的结构和算法,性能提升很多。但恶意插入还是比正常插入耗时长的多。

    我们再写一个简单的接口,利用刚才生成的恶意的数据来模拟攻击,同时利用top命令看看系统资源消耗。
    脚本:

    $json = file_get_contents('php://input');
    $startTime = microtime(true);
    $arr = json_decode($json, true);
    $endTime = microtime(true);
    echo "解析数据耗时 ", $endTime - $startTime, " s\n";
    

    攻击前的系统资源占用情况:
    在这里插入图片描述

    下面利用curl模拟攻击:
    在这里插入图片描述

    同时使用top命令查看:
    在这里插入图片描述
    可以看到cpu资源被耗尽,这还只是一条请求,如果使用ab压力测试,cpu资源会长时间被耗尽。
    可以使用以下命令模拟:

    ab -n 1000 -c 100 -s 1000 -p './hash.json' http://127.0.0.1/test2.php
    

    会发现cpu一直处于100%,要想停下来,只有重启php-fpm

    service php-fpm restart
    

    至于hash碰撞攻击的原理,网上很多,我就不详细说了,下面说说怎么防御。

    三、hash碰撞攻击的防御

    其实不光json_decode有这个问题,牵扯到数组的都会有这个问题。比如GET/POST/COOKIE,均有此问题。

    GET/POST/COOKIE可以通过设置max_input_vars来解决。打开php.ini文件,找到此位置,修改其值,默认大小是1000。
    在这里插入图片描述

    至于json_decode这个问题,通常都是在接口容易被攻击。
    所以通常用以下方法解决:
    1、加密传输数据,自家应用接口可以采用对称加密,因为加解密速度快。开放给第三方的接口可以使用非对称加密,虽然加解密速度没有对称加密快,但是更安全点。
    2、使用$json = file_get_contents('php://input');获取数据后校验数据长度,根据业务特点规定最大长度,超过这个长度就报错。
    3、也可以将接收到的数据转为json对象,而不转成数组。即json_decode()函数不要设置第二个参数为true
    4、使用php7以上版本,php7相比于php5,性能提升非常多。

    展开全文
  • 提出了对DHA-256散列函数37轮的原像攻击以及39轮的伪碰撞攻击。基于中间相遇攻击,利用Biclique方法可以改进之前对DHA-256的原像分析结果,将攻击轮数从原来的35轮提高到了37轮。通过上述方法还可以构造对DHA-256的...
  • 提出了对SM3散列函数32轮的原像攻击和33轮的伪碰撞攻击。利用差分中间相遇攻击与biclique技术改进了对SM3的原像分析结果,将攻击结果从之前的30轮提高到了32轮。基于上述方法,通过扩展32轮原像攻击中的差分路径,对...
  • 搜索SHA-1碰撞攻击扰动向量的新策略
  • 改进的MD4有意义碰撞攻击,md4论文,中文 pdf版,可粘贴复制
  • 给出了对RIPEMD-128后三圈的碰撞攻击路线,理论上使其碰撞概率为2-55,低于生日攻击的2-64的概率.
  • 改进了对MD4的有意义的碰撞攻击
  • 针对FPGA实现的DES算法旁路碰撞攻击研究.pdf
  • 哈希碰撞攻击

    千次阅读 2013-12-18 00:00:58
    哈希表碰撞攻击的基本原理 哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息...
     
    

    哈希表碰撞攻击的基本原理

    哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希表结构存储)。

    理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值(key),然后在常量时间内定位到一个桶(术语bucket,表示哈希表中的一个位置)。当然这是理想情况下,因为任何哈希表的长度都是有限的,所以一定存在不同的数据项具有相同哈希值的情况,此时不同数据项被定为到同一个桶,称为碰撞(collision)。哈希表的实现需要解决碰撞问题,碰撞解决大体有两种思路,第一种是根据某种原则将被碰撞数据定为到其它桶,例如线性探测——如果数据在插入时发生了碰撞,则顺序查找这个桶后面的桶,将其放入第一个没有被使用的桶;第二种策略是每个桶不是一个只能容纳单个数据项的位置,而是一个可容纳多个数据的数据结构(例如链表或红黑树),所有碰撞的数据以某种数据结构的形式组织起来。

    不论使用了哪种碰撞解决策略,都导致插入和查找操作的时间复杂度不再是O(1)。以查找为例,不能通过key定位到桶就结束,必须还要比较原始key(即未做哈希之前的key)是否相等,如果不相等,则要使用与插入相同的算法继续查找,直到找到匹配的值或确认数据不在哈希表中。

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

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

    可以看到,进行哈希碰撞攻击的前提是哈希算法特别容易找出碰撞,如果是MD5或者SHA1那基本就没戏了,幸运的是(也可以说不幸的是)大多数编程语言使用的哈希算法都十分简单(这是为了效率考虑),因此可以不费吹灰之力之力构造出攻击数据。下一节将通过分析Zend相关内核代码,找出攻击哈希表碰撞攻击PHP的方法。

    Zend哈希表的内部实现

    PHP中使用一个叫Backet的结构体表示桶,同一哈希值的所有桶被组织为一个单链表。哈希表使用HashTable结构体表示。相关源码在zend/Zend_hash.h下:

    typedef struct bucket {
        ulong h;                        /* Used for numeric indexing */
        uint nKeyLength;
        void *pData;
        void *pDataPtr;
        struct bucket *pListNext;
        struct bucket *pListLast;
        struct bucket *pNext;
        struct bucket *pLast;
        char arKey[1]; /* Must be last element */
    } Bucket;
     
    typedef struct _hashtable {
        uint nTableSize;
        uint nTableMask;
        uint nNumOfElements;
        ulong nNextFreeElement;
        Bucket *pInternalPointer;   /* Used for element traversal */
        Bucket *pListHead;
        Bucket *pListTail;
        Bucket **arBuckets;
        dtor_func_t pDestructor;
        zend_bool persistent;
        unsigned char nApplyCount;
        zend_bool bApplyProtection;
    #if ZEND_DEBUG
        int inconsistent;
    #endif
    } HashTable;

    字段名很清楚的表明其用途,因此不做过多解释。重点明确下面几个字段:Bucket中的“h”用于存储原始key;HashTable中的nTableMask是一个掩码,一般被设为nTableSize – 1,与哈希算法有密切关系,后面讨论哈希算法时会详述;arBuckets指向一个指针数组,其中每个元素是一个指向Bucket链表的头指针。

    哈希算法:PHP哈希表最小容量是8(2^3),最大容量是0×80000000(2^31),并向2的整数次幂圆整(即长度会自动扩展为2的整数次幂,如13个元素的哈希表长度为16;100个元素的哈希表长度为128)。nTableMask被初始化为哈希表长度(圆整后)减1。具体代码在zend/Zend_hash.c的_zend_hash_init函数中,这里截取与本文相关的部分并加上少量注释。

    ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
    {
        uint i = 3;
        Bucket **tmp;
     
        SET_INCONSISTENT(HT_OK);
     
        //长度向2的整数次幂圆整
        if (nSize >= 0x80000000) {
            /* prevent overflow */
            ht->nTableSize = 0x80000000;
        } else {
            while ((1U << i) < nSize) { //1u = unsigned
                i++;
            }
            ht->nTableSize = 1 << i;
        }
     
        ht->nTableMask = ht->nTableSize - 1;
     
        /*此处省略若干代码…*/
     
        return SUCCESS;
    }

    值得一提的是PHP向2的整数次幂取圆整方法非常巧妙,可以背下来在需要的时候使用。

    Zend HashTable的哈希算法很简单:hash(key)=key&nTableMask

    即简单将数据的原始key与HashTable的nTableMask进行按位与即可。如果原始key为字符串,则首先使用Times33算法将字符串转为整形再与nTableMask按位与:hash(strkey)=time33(strkey)&nTableMask

    下面是Zend源码中查找哈希表的代码:

    ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData)
    {
        uint nIndex;
        Bucket *p;
     
        IS_CONSISTENT(ht);
     
        nIndex = h & ht->nTableMask;
     
        p = ht->arBuckets[nIndex];
        while (p != NULL) {
            if ((p->h == h) && (p->nKeyLength == 0)) {
                *pData = p->pData;
                return SUCCESS;
            }
            p = p->pNext;
        }
        return FAILURE;
    }
     
    ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)
    {
        ulong h;
        uint nIndex;
        Bucket *p;
     
        IS_CONSISTENT(ht);
     
        h = zend_inline_hash_func(arKey, nKeyLength);
        nIndex = h & ht->nTableMask;
     
        p = ht->arBuckets[nIndex];
        while (p != NULL) {
            if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
                if (!memcmp(p->arKey, arKey, nKeyLength)) { //比较字符key是否相等(nKeyLength长度))
                    *pData = p->pData;
                    return SUCCESS;
                }
            }
            p = p->pNext;
        }
        return FAILURE;
    }

    其中zend_hash_index_find用于查找整数key的情况,zend_hash_find用于查找字符串key。逻辑基本一致,只是字符串key会通过zend_inline_hash_func转为整数key,zend_inline_hash_func封装了times33算法,具体代码就不贴出了。

    攻击

    知道了PHP内部哈希表的算法,就可以利用其原理构造用于攻击的数据。一种最简单的方法是利用掩码规律制造碰撞。上文提到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;
    }
     //$key += $size 大于2的16次方
    $endTime = microtime(true);
    echo $endTime - $startTime, ' seconds', "\n";
    ?>

    当然,一般情况下很难遇到攻击者可以直接修改PHP代码的情况,但是攻击者仍可以通过一些方法间接构造哈希表来进行攻击。例如PHP会将接收到的HTTP POST请求中的数据构造为$_POST,而这是一个Array,内部就是通过Zend HashTable表示,因此攻击者只要构造一个含有大量碰撞key的post请求,就可以达到攻击的目的。具体做法不再演示。

    防御

    POST攻击的防护:针对POST方式的哈希碰撞攻击,目前PHP的防护措施是控制POST数据的数量。在>=PHP5.3.9的版本中增加了一个配置项max_input_vars,用于标识一次http请求最大接收的参数个数,默认为1000。因此PHP5.3.x的用户可以通过升级至5.3.9来避免哈希碰撞攻击。5.2.x的用户可以使用这个patch:http://www.laruence.com/2011/12/30/2440.html。

    另外的防护方法是在Web服务器层面进行处理,例如限制http请求body的大小和参数的数量等,这个是现在用的最多的临时处理方案。具体做法与不同Web服务器相关,不再详述。

    上面的防护方法只是限制POST数据的数量,而不能彻底解决这个问题。例如,如果某个POST字段是一个json数据类型,会被PHP json_decode,那么只要构造一个超大的json攻击数据照样可以达到攻击目的。理论上,只要PHP代码中某处构造Array的数据依赖于外部输入,则都可能造成这个问题,因此彻底的解决方案要从Zend底层HashTable的实现动手。一般来说有两种方式,一是限制每个桶链表的最长长度;二是使用其它数据结构如红黑树取代链表组织碰撞哈希(并不解决哈希碰撞,只是减轻攻击影响,将N个数据的操作时间从O(N^2)降至O(NlogN),代价是普通情况下接近O(1)的操作均变为O(logN))。

    目前使用最多的仍然是POST数据攻击,因此建议生产环境的PHP均进行升级或打补丁。至于从数据结构层面修复这个问题,目前还没有任何方面的消息。


    展开全文
  • 在搭建的真实攻击环境下,验证了文献[8]中的碰撞攻击在ASIC真实环境中攻击效果并不明显的结论。应用改进的方法后,一对功耗曲线样本便可恢复出88%以上的密钥,实现了小样本量曲线的RSA指数的提取。讨论了对这种攻击方法...
  • 对7轮AES-192的碰撞攻击进行了改进.改进的碰撞攻击是基于Gilbert和Minier的攻击过程并且利用了一些AES-192的密钥特性.改进的攻击可以使用2123字节的存储通过大约2120次加密运算来恢复主密钥,此过程比 Gilbert和...
  • 减少RIPEMD-160的新的半自由启动碰撞攻击框架
  • HAVAL-128上的一种新的一比特差异碰撞攻击
  • PHP哈希表碰撞攻击原理

    千次阅读 2016-01-05 13:02:56
    最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。 哈希表碰撞攻击的基本原理 哈希表是一种查找效率极高的...

    最近哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。

    哈希表碰撞攻击的基本原理

    哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希表结构存储)。

    理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值(key),然后在常量时间内定位到一个桶(术语bucket,表示哈希表中的一个位置)。当然这是理想情况下,因为任何哈希表的长度都是有限的,所以一定存在不同的数据项具有相同哈希值的情况,此时不同数据项被定为到同一个桶,称为碰撞(collision)。哈希表的实现需要解决碰撞问题,碰撞解决大体有两种思路,第一种是根据某种原则将被碰撞数据定为到其它桶,例如线性探测——如果数据在插入时发生了碰撞,则顺序查找这个桶后面的桶,将其放入第一个没有被使用的桶;第二种策略是每个桶不是一个只能容纳单个数据项的位置,而是一个可容纳多个数据的数据结构(例如链表或红黑树),所有碰撞的数据以某种数据结构的形式组织起来。

    不论使用了哪种碰撞解决策略,都导致插入和查找操作的时间复杂度不再是O(1)。以查找为例,不能通过key定位到桶就结束,必须还要比较原始key(即未做哈希之前的key)是否相等,如果不相等,则要使用与插入相同的算法继续查找,直到找到匹配的值或确认数据不在哈希表中。

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

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

    可以看到,进行哈希碰撞攻击的前提是哈希算法特别容易找出碰撞,如果是MD5或者SHA1那基本就没戏了,幸运的是(也可以说不幸的是)大多数编程语言使用的哈希算法都十分简单(这是为了效率考虑),因此可以不费吹灰之力之力构造出攻击数据。下一节将通过分析Zend相关内核代码,找出攻击哈希表碰撞攻击PHP的方法。

    Zend哈希表的内部实现

    数据结构

    PHP中使用一个叫Backet的结构体表示桶,同一哈希值的所有桶被组织为一个单链表。哈希表使用HashTable结构体表示。相关源码在zend/Zend_hash.h下:

     
    1. typedef struct bucket {
    2. ulong h; /* Used for numeric indexing */
    3. uint nKeyLength;
    4. void *pData;
    5. void *pDataPtr;
    6. struct bucket *pListNext;
    7. struct bucket *pListLast;
    8. struct bucket *pNext;
    9. struct bucket *pLast;
    10. char arKey[1]; /* Must be last element */
    11. } Bucket;
    12.  
    13. typedef struct _hashtable {
    14. uint nTableSize;
    15. uint nTableMask;
    16. uint nNumOfElements;
    17. ulong nNextFreeElement;
    18. Bucket *pInternalPointer; /* Used for element traversal */
    19. Bucket *pListHead;
    20. Bucket *pListTail;
    21. Bucket **arBuckets;
    22. dtor_func_t pDestructor;
    23. zend_bool persistent;
    24. unsigned char nApplyCount;
    25. zend_bool bApplyProtection;
    26. #if ZEND_DEBUG
    27. int inconsistent;
    28. #endif
    29. } HashTable;

    字段名很清楚的表明其用途,因此不做过多解释。重点明确下面几个字段:Bucket中的“h”用于存储原始key;HashTable中的nTableMask是一个掩码,一般被设为nTableSize - 1,与哈希算法有密切关系,后面讨论哈希算法时会详述;arBuckets指向一个指针数组,其中每个元素是一个指向Bucket链表的头指针。

    哈希算法

    PHP哈希表最小容量是8(2^3),最大容量是0x80000000(2^31),并向2的整数次幂圆整(即长度会自动扩展为2的整数次幂,如13个元素的哈希表长度为16;100个元素的哈希表长度为128)。nTableMask被初始化为哈希表长度(圆整后)减1。具体代码在zend/Zend_hash.c的_zend_hash_init函数中,这里截取与本文相关的部分并加上少量注释。

     
    1. ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
    2. {
    3. uint i = 3;
    4. Bucket **tmp;
    5.  
    6. SET_INCONSISTENT(HT_OK);
    7.  
    8. //长度向2的整数次幂圆整
    9. if (nSize >= 0x80000000) {
    10. /* prevent overflow */
    11. ht->nTableSize = 0x80000000;
    12. } else {
    13. while ((1U << i) < nSize) {
    14. i++;
    15. }
    16. ht->nTableSize = 1 << i;
    17. }
    18.  
    19. ht->nTableMask = ht->nTableSize - 1;
    20.  
    21. /*此处省略若干代码…*/
    22.  
    23. return SUCCESS;
    24. }

    值得一提的是PHP向2的整数次幂取圆整方法非常巧妙,可以背下来在需要的时候使用。

    Zend HashTable的哈希算法异常简单:

    hash(key)=key & nTableMask

    即简单将数据的原始key与HashTable的nTableMask进行按位与即可。

    如果原始key为字符串,则首先使用Times33算法将字符串转为整形再与nTableMask按位与。

    hash(strkey)=time33(strkey) & nTableMask

    下面是Zend源码中查找哈希表的代码:

     
    1. ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData)
    2. {
    3. uint nIndex;
    4. Bucket *p;
    5.  
    6. IS_CONSISTENT(ht);
    7.  
    8. nIndex = h & ht->nTableMask;
    9.  
    10. p = ht->arBuckets[nIndex];
    11. while (p != NULL) {
    12. if ((p->h == h) && (p->nKeyLength == 0)) {
    13. *pData = p->pData;
    14. return SUCCESS;
    15. }
    16. p = p->pNext;
    17. }
    18. return FAILURE;
    19. }
    20.  
    21. ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)
    22. {
    23. ulong h;
    24. uint nIndex;
    25. Bucket *p;
    26.  
    27. IS_CONSISTENT(ht);
    28.  
    29. h = zend_inline_hash_func(arKey, nKeyLength);
    30. nIndex = h & ht->nTableMask;
    31.  
    32. p = ht->arBuckets[nIndex];
    33. while (p != NULL) {
    34. if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
    35. if (!memcmp(p->arKey, arKey, nKeyLength)) {
    36. *pData = p->pData;
    37. return SUCCESS;
    38. }
    39. }
    40. p = p->pNext;
    41. }
    42. return FAILURE;
    43. }

    其中zend_hash_index_find用于查找整数key的情况,zend_hash_find用于查找字符串key。逻辑基本一致,只是字符串key会通过zend_inline_hash_func转为整数key,zend_inline_hash_func封装了times33算法,具体代码就不贴出了。

    攻击

    基本攻击

    知道了PHP内部哈希表的算法,就可以利用其原理构造用于攻击的数据。一种最简单的方法是利用掩码规律制造碰撞。上文提到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。

    下面是利用这个原理写的一段攻击代码:

     
    1. <?php
    2.  
    3. $size = pow(2, 16);
    4.  
    5. $startTime = microtime(true);
    6.  
    7. $array = array();
    8. for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
    9. $array[$key] = 0;
    10. }
    11.  
    12. $endTime = microtime(true);
    13.  
    14. echo $endTime - $startTime, ' seconds', "\n";

    这段代码在我的VPS上(单CPU,512M内存)上用了近88秒才完成,并且在此期间CPU资源几乎被用尽:

    而普通的同样大小的哈希表插入仅用时0.036秒:

     
    1. <?php
    2.  
    3. $size = pow(2, 16);
    4.  
    5. $startTime = microtime(true);
    6.  
    7. $array = array();
    8. for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $size; $key += 1) {
    9. $array[$key] = 0;
    10. }
    11.  
    12. $endTime = microtime(true);
    13.  
    14. echo $endTime - $startTime, ' seconds', "\n";

    可以证明第二段代码插入N个元素的时间在O(N)水平,而第一段攻击代码则需O(N^2)的时间去插入N个元素。

    POST攻击

    当然,一般情况下很难遇到攻击者可以直接修改PHP代码的情况,但是攻击者仍可以通过一些方法间接构造哈希表来进行攻击。例如PHP会将接收到的HTTP POST请求中的数据构造为$_POST,而这是一个Array,内部就是通过Zend HashTable表示,因此攻击者只要构造一个含有大量碰撞key的post请求,就可以达到攻击的目的。具体做法不再演示。

    防护

    POST攻击的防护

    针对POST方式的哈希碰撞攻击,目前PHP的防护措施是控制POST数据的数量。在>=PHP5.3.9的版本中增加了一个配置项max_input_vars,用于标识一次http请求最大接收的参数个数,默认为1000。因此PHP5.3.x的用户可以通过升级至5.3.9来避免哈希碰撞攻击。5.2.x的用户可以使用这个patch:http://www.laruence.com/2011/12/30/2440.html

    另外的防护方法是在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均进行升级或打补丁。至于从数据结构层面修复这个问题,目前还没有任何方面的消息。

    参考

    [1] Supercolliding a PHP array

    [2] PHP5.2.*防止Hash冲突拒绝服务攻击的Patch

    [3] 通过构造Hash冲突实现各种语言的拒绝服务攻击

    [4] PHP数组的Hash冲突实例

    [5] PHP 5.4.0 RC4 released



    from: http://blog.codinglabs.org/articles/hash-collisions-attack-on-php.html

    展开全文
  • 1.引子 哈希表的原理是用数组来保存键值对,键值对存放的位置(下标)由键的哈希值决定,键的哈希值可以在参数时间内计算出来,这样哈希表插入、查找和删除的时间复杂度为O(1),但是这是...所谓的哈希碰撞攻击就是,针
  • 从实践出发,继弱类型变量原理探究后,王帅将继续带大家弄清PHP内核中的一些常用部分,本期则是内核利器哈希表与哈希碰撞攻击。在PHP的Zend Engine(下面简称ZE)中,有一个非常重要的数据结构——哈希表...
  • 这是迄今为止第一个让我觉得后怕的攻击方式,涉及的范围广难以防御,攻击效果...大量的网站和Web接口都未做Hash碰撞攻击的防御,一拿一个准。 随着RESTful风格的接口普及,程序员默认都会使用json作为数据传递的方式。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,815
精华内容 5,926
关键字:

碰撞攻击