php中的哈希碰撞以及防護

php中的哈希表

php中的變量是以符號表的方式進行存儲的,實際上也是個HashTable,哈希表是經過特定的哈希算法將索引轉換成特定的index而後映射到對應的槽中,而後採用拉鍊法,在一個槽中使用鏈表將數據進行存儲,鏈表的時間複雜度爲O(n)。php

php中的hashtable的結構定義在Zend/zend_hash.h文件中:web

//保存數據的單鏈表結構
typedef struct bucket {
   ulong h;                  /* Used for numeric indexing */
   uint nKeyLength;        //key長度
   void *pData;        //指向bucket中保存的數據的指針
   void *pDataPtr;    //指針數據
   struct bucket *pListNext;    //指向hashtable桶列中下一個元素
   struct bucket *pListLast;    //指向hashtable桶列前一個元素
   struct bucket *pNext;        //指向具備同一個hash index的桶列的後一個元素
   struct bucket *pLast;        //指向具備同一個hash index的桶列的前一個元素
   const char *arKey;        //必須是最後一個成員,key的名稱
} Bucket;

每一個數據元素bucket有一個鍵名key,它在整個hashtable中是惟一的,不能重複;根據鍵名能夠惟一肯定hashtable中的數據元素
在php的數組中,鍵的值能夠是整型或字符串,在這裏也只有這兩種形式:算法

  • 若是key爲字符串的話:字符串arKey做爲鍵名,該字符串的長度爲nKeyLengthh字段保存arKey對應的hash值json

  • 索引方式: 這時nKeyLength爲0,索引值存儲在h上,也就是數據元素的鍵名數組

bucket是保存在hashtable上的一個雙向鏈表,其向前和向後的元素分別用pLastpNext來表示。新插入的Becket放在該桶的最前面
Bucket中,實際上數據是保存在pData指針指向的內存塊中的,一般這個內存塊是系統額外分配的。可是存在例外,當Bucket保存的數據是一個指針的時候,Hashtable不會另外請求系統分配空間來保存這個指針,而是直接將該指針保存到pDataPtr中,而後再將pData指向本結構成員的地址服務器

hashtable中全部的bucket經過pListNext,pListLast構成了一個雙向鏈表。最新插入的Bucket放在雙向鏈表的最後面數據結構

//hashtable結構體
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;

hash表的主要保存的數據包括兩部分:哈希表中存儲的數據的個數;保存數據的容器
HashTable結構中,nTableSize指定了HashTable的大小,同時也限定了哈希表中能保存的Bucket的數量,該數值越大,系統爲HashTable分配的內存就越多。爲了提升計算效率, 系統自動會將nTableSize調整到最小一個不小於nTableSize的2的整數次方,這個其實不太懂
arBucketsHashTable的關鍵,HashTable會自動向系統申請一塊內存,並將其地址賦值給arBuckets,該內存大小正好容納nTableSize指針。咱們能夠將arBuckets看做一個大小爲nTableSize的數組,每一個數組元素都是一個指針,用於指向存放數據的Buckets。在初始化的時候每一個指針都爲null函數

nTableMask的值永遠是nTableSize - 1,引入這個字段的主要目的是爲了提升計算效率,爲了快速計算Buckets鍵名在arBuckets數組中的索引post

nNumberOfElements記錄了HastTable當前保存的數據元素的個數。當nNumberOfElements大於nTableSize的時候,HashTable就自動擴展爲原來的兩倍大小ui

nNextFreeElement記錄HashTable中下一個可用於插入數據元素的arBuckets索引

pListHead,pListTail分別表示Buckets雙向鏈表的第一個和最後一個元素,這些元素一般是根據插入的順序排列的。也能夠根據對應的排序函數對其進行從新排列。pInternalPointer則用於在遍歷HashTable時記錄當前遍歷的位置,它是一個指針,指向當前遍歷到的Bucket,初始值是pListHead

pDestroyctor是一個函數指針,在HashTable的增長,修改,刪除Bucket時自動調用,用於處理相關數據的清理工做

persistent標誌位指出了Bucket內存分配的方式。若是persisient爲TRUE,則使用操做系統自己的內存分配函數爲Bucket分配內存,不然使用PHP的內存分配函數。具體請參考PHP的內存管理。

php中爲了使用的哈希算法是DJBX33A

哈希碰撞

php中是使用單鏈表去存儲碰撞的數據的,因此實際上php在哈希表上的平均查找複雜度爲O(L),其中L爲桶鏈表的平均長度;而最壞的時間複雜度爲O(N),此時因此存儲到hashtable上的數據所有發生碰撞,哈希表退化成爲單鏈表。

哈希表碰撞就是精心設計的數據使全部的數據發生碰撞。人爲的將一個哈希表退化成爲一個單鏈表,在哈希表上的各類操做的時間會提高一個數量級,大量消耗CPU,致使系統沒法快速響應請求,從而達到拒絕服務(DDOS)的目的

<?php
$size = pow(2, 16);
$startTime = microtime(true);

$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
    var_dump($key);
    //$array[$key] = 0;
}

$endTime = microtime(true);
echo $endTime - $startTime, ' seconds', "\n";
?>

通常狀況下很難直接修改php的代碼去製造哈希碰撞攻擊,可是在表單參數$_POST是一個Array,內部就是經過Zend HashTable來實現的,攻擊者只要構造一個含有大量碰撞的key的post請求,就能夠達到ddos攻擊的目的

哈希表碰撞的防護

以上說了經過http post請求中的表單數據進行攻擊,防護的方式能夠:

  • 在php5.3以上的版本中,post參數的數量存在最大的限制max_input_vars => 1000

  • 在web服務器層面進行處理,如經過限制請求body大小和參數的數量等,這個也是目前使用最多的解決方案

  • 其實以上的兩種解決方案並不能解決問題,由於只是單純的在參數的數量上進行限制了,可是入股請求的數據中包含json數據,但其中的數據就是碰撞的array。理論上,只要php代碼某處構造array的數據依賴於外部輸入,則均可能形成這個問題,所以完全的解決方案是更改Zend底層的HashTable實現

    • 限制每一個桶鏈表的最長長度

    • 使用其餘數據結構如紅黑樹取代鏈表組織碰撞哈希(並不解決哈希碰撞,只是減輕攻擊影響,將N個數據的操做時間從O(N^2)降至O(NlogN),代價是普通狀況下接近O(1)的操做均變爲O(logN))

    參考:

php內核探索:http://www.nowamagic.net/libr...

相關文章
相關標籤/搜索