Lua/PHP哈希碰撞攻击浅析4. PHP中哈希表实现
● PHP数组为纯哈希表,无通常意义上的数组部分,所有下标
均作为key看待,使用按哈希值取模分桶方式保存键值对,靠
外部拉链解决冲突
● 初始分桶数为2^3=8个(_zend_hash_init@zend_hash.c:140)
● 插入哈希表的键值对数量超过分桶数后,分桶数量倍增并将
所有键值对重新哈希(zend_hash_do_resize@zend_hash.c:418)
● 数值(或数值字符串)下标的key被转换成整数后直接当作哈
希值使用,即H(n)=n(爆破点!)
zend_fetch_dimension_address (zend_execute.c:903)->
zend_fetch_dimension_address_inner (zend_execute.c:798) ->
zend_symtable_update (zend_hash.h:343)->
(由 ZEND_HANDLE_NUMERIC 判定为类数值字符串后)->
_zend_hash_index_update_or_next_insert (zend_hash.c:350)
5. PHP中哈希表实现
● 字符串哈希算法为DJBX33A,全部键内容均参与哈希值计算
(zend_inline_hash_func@zend_hash.h:261)
● 制造哈希碰撞稍微复杂一些,但并非不可能!
zend_fetch_dimension_address (zend_execute.c:903)->
zend_fetch_dimension_address_inner (zend_execute.c:798) ->
zend_symtable_update (zend_hash.h:343)->
(由 ZEND_HANDLE_NUMERIC 判定 为非数值字符串后)->
_zend_hash_add_or_update (zend_hash.c:203)
6. PHP哈希碰撞攻击原理
● PHP处理请求时会将uri参数、POST请求体和cookie的全部内
容分别解析到特殊PHP数组$_GET、$_POST和$_COOKIE 中
(php_default_treat_data@php_variables.c:292、php_content_types.c:30、
)
php_std_post_handler@php_variables.c:249
● 在uri参数、POST请求体或cookie中构造从x开始的n个整数
key,依次递增步长m(m为2^N,N>=log2(n)),即可保证解
析时n个key都落在一个桶内,实现哈希值完全碰撞
● 例如:n=64k,m=2^16
8. Lua中哈希表实现
● Lua table同时包含数组和哈希表部分,数值下标先在数组部
分查找再到哈希表中查找
● 以哈希值取模分桶方式保存键值对,靠内部拉链解决冲突
● 哈希表部分初始分桶数为0(luaH_new@ltable.c:358)
● 插入哈希表的键值对数量超过分桶数后,分桶数量倍增并将
所有键值对重新哈希(rehash@ltable.c:333)
● 数值、userdata、table等下标对(n-1)取模,字符串、
boolean等下标对n取模(n为分桶数,总是2^k形式)
(mainposition@ltable.c:100)
● 数值下标(通常为double型)按32bit分为多段后累加得到哈
希值(hashnum@ltable.c:84)(结合之前的取模规则较难攻击)
9. Lua中哈希
● 由于Lua中字符串是常量,故字符串下标创建时即计算出了对
应的哈希值(luaS_newlstr@lstring.c:75)
● 计算字符串哈希值时,并非字符串全部内容都参与计算,而
是从后向前按步长k抽出字符计算,这里k=(L>>5)+1(爆破
点!)
● 为节约存储空间,Lua将所有字符串加入全局字符串哈希表,
新建字符串若已在全局表中存在则不再创建新实例。全局字
符串表为分桶外部拉链结构,分桶数量按需倍增,按字符串哈
希值对分桶数量取模进行分桶(另一个爆破点!)
10. Lua潜在的哈希碰撞攻击
● 利用字符串哈希值计算只挑选特定位置字符的特点,保证这
些位置上的字符不变构造不同的字符串,使其哈希值完全相
同,实现完全碰撞。
● 例如:固定字符串长度为32,保证从尾部向前每间隔1个字符
都相同。
○ x00 x11 x00 ... x00 x11 x00
○ x00 x22 x00 ... x00 x22 x00
○ x00 x33 x00 ... x00 x33 x00
○ ...
11. Lua哈希碰撞示例
local t = {}
local n = 10000
for i = 1, n do
local s = ('0'):rep(28) .. string.char(i/255) .. '0' ..
string.char(i%255) .. '0'
-- global string table colliding
t[#t+1] = s
-- global string table and table hash colliding
--t[s] = 1
end
14. LuaJIT哈希碰撞示例
local t = {}
local n = 10000
for i = 1, n do
local s = ('0'):rep(18) .. string.char(i/255) .. string.char
(i%255) .. ('0'):rep(12)
-- global string table colliding
t[#t+1] = s
-- global string table and table hash colliding
--t[s] = 1
end