理想狀況下哈希表插入和查找操做的時間複雜度均爲O(1),任何一個數據項能夠在一個與哈希表長度無關的時間內計算出一個哈希值(key),而後在常量時間內定位到一個桶(術語bucket,表示哈希表中的一個位置)。固然這是理想狀況下,由於任何哈希表的長度都是有限的,因此必定存在不一樣的數據項具備相同哈希值的狀況,此時不一樣數據項被定爲到同一個桶,稱爲碰撞(collision)。哈希表的實現須要解決碰撞問題,碰撞解決大致有兩種思路,第一種是根據某種原則將被碰撞數據定爲到其它桶,例如線性探測——若是數據在插入時發生了碰撞,則順序查找這個桶後面的桶,將其放入第一個沒有被使用的桶;第二種策略是每一個桶不是一個只能容納單個數據項的位置,而是一個可容納多個數據的數據結構(例如鏈表或紅黑樹),全部碰撞的數據以某種數據結構的形式組織起來。php
不論使用了哪一種碰撞解決策略,都致使插入和查找操做的時間複雜度再也不是O(1)。以查找爲例,不能經過key定位到桶就結束,必須還要比較原始key(即未作哈希以前的key)是否相等,若是不相等,則要使用與插入相同的算法繼續查找,直到找到匹配的值或確認數據不在哈希表中。算法
知道了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。下面是利用這個原理寫的一段攻擊代碼:blog
<?php $size = pow(2, 16); $startTime = microtime(true); $array = array(); for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) { $array[$key] = 0; } $endTime = microtime(true); echo $endTime - $startTime, ' seconds', "\n"; ?>