理想状况下哈希表插入和查找操做的时间复杂度均为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"; ?>