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
做爲鍵名,該字符串的長度爲nKeyLength
,h
字段保存arKey
對應的hash值json
索引方式: 這時nKeyLength
爲0,索引值存儲在h
上,也就是數據元素的鍵名數組
bucket
是保存在hashtable
上的一個雙向鏈表,其向前和向後的元素分別用pLast
和pNext
來表示。新插入的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的整數次方,這個其實不太懂arBuckets
是HashTable
的關鍵,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...