background image

哈希表碰撞攻击就是通过精心构造数据,使得所有数据全部碰撞,人为将哈希表变成一个
退化的单链表,此时哈希表各种操作的时间均提升了一个数量级,因此会消耗大量

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),最大容量是 0x80000000(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-