色综合图-色综合图片-色综合图片二区150p-色综合图区-玖玖国产精品视频-玖玖香蕉视频

您的位置:首頁技術(shù)文章
文章詳情頁

PHP內(nèi)核探索 —— 哈希碰撞攻擊是什么:攻擊的原理及實現(xiàn)

瀏覽:31日期:2022-09-16 14:18:29

最近哈希表碰撞攻擊(Hashtable collisions as DOS attack)的話題不斷被提起,各種語言紛紛中招。本文結(jié)合PHP內(nèi)核源碼,聊一聊這種攻擊的原理及實現(xiàn)。

哈希表碰撞攻擊的基本原理

哈希表是一種查找效率極高的數(shù)據(jù)結(jié)構(gòu),很多語言都在內(nèi)部實現(xiàn)了哈希表。PHP中的哈希表是一種極為重要的數(shù)據(jù)結(jié)構(gòu),不但用于表示Array數(shù)據(jù)類型,還在Zend虛擬機內(nèi)部用于存儲上下文環(huán)境信息(執(zhí)行上下文的變量及函數(shù)均使用哈希表結(jié)構(gòu)存儲)。

理想情況下哈希表插入和查找操作的時間復雜度均為O(1),任何一個數(shù)據(jù)項可以在一個與哈希表長度無關的時間內(nèi)計算出一個哈希值(key),然后在常量時間內(nèi)定位到一個桶(術(shù)語bucket,表示哈希表中的一個位置)。當然這是理想情況下,因為任何哈希表的長度都是有限的,所以一定存在不同的數(shù)據(jù)項具有相同哈希值的情況,此時不同數(shù)據(jù)項被定為到同一個桶,稱為碰撞(collision)。哈希表的實現(xiàn)需要解決碰撞問題,碰撞解決大體有兩種思路,第一種是根據(jù)某種原則將被碰撞數(shù)據(jù)定為到其它桶,例如線性探測——如果數(shù)據(jù)在插入時發(fā)生了碰撞,則順序查找這個桶后面的桶,將其放入第一個沒有被使用的桶;第二種策略是每個桶不是一個只能容納單個數(shù)據(jù)項的位置,而是一個可容納多個數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)(例如鏈表或紅黑樹),所有碰撞的數(shù)據(jù)以某種數(shù)據(jù)結(jié)構(gòu)的形式組織起來。

不論使用了哪種碰撞解決策略,都導致插入和查找操作的時間復雜度不再是O(1)。以查找為例,不能通過key定位到桶就結(jié)束,必須還要比較原始key(即未做哈希之前的key)是否相等,如果不相等,則要使用與插入相同的算法繼續(xù)查找,直到找到匹配的值或確認數(shù)據(jù)不在哈希表中。

PHP是使用單鏈表存儲碰撞的數(shù)據(jù),因此實際上PHP哈希表的平均查找復雜度為O(L),其中L為桶鏈表的平均長度;而最壞復雜度為O(N),此時所有數(shù)據(jù)全部碰撞,哈希表退化成單鏈表。下圖PHP中正常哈希表和退化哈希表的示意圖。

PHP內(nèi)核探索 —— 哈希碰撞攻擊是什么:攻擊的原理及實現(xiàn)

哈希表碰撞攻擊就是通過精心構(gòu)造數(shù)據(jù),使得所有數(shù)據(jù)全部碰撞,人為將哈希表變成一個退化的單鏈表,此時哈希表各種操作的時間均提升了一個數(shù)量級,因此會消耗大量CPU資源,導致系統(tǒng)無法快速響應請求,從而達到拒絕服務攻擊(DoS)的目的。

可以看到,進行哈希碰撞攻擊的前提是哈希算法特別容易找出碰撞,如果是MD5或者SHA1那基本就沒戲了,幸運的是(也可以說不幸的是)大多數(shù)編程語言使用的哈希算法都十分簡單(這是為了效率考慮),因此可以不費吹灰之力之力構(gòu)造出攻擊數(shù)據(jù)。下一節(jié)將通過分析Zend相關內(nèi)核代碼,找出攻擊哈希表碰撞攻擊PHP的方法。

Zend哈希表的內(nèi)部實現(xiàn)

PHP中使用一個叫Backet的結(jié)構(gòu)體表示桶,同一哈希值的所有桶被組織為一個單鏈表。哈希表使用HashTable結(jié)構(gòu)體表示。相關源碼在zend/Zend_hash.h下:

typedef struct bucket { ulong h;/* Used for numeric indexing */ uint nKeyLength; void *pData; void *pDataPtr; struct bucket *pListNext; struct bucket *pListLast; struct bucket *pNext; struct bucket *pLast; char arKey[1]; /* Must be last element */} Bucket;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;

字段名很清楚的表明其用途,因此不做過多解釋。重點明確下面幾個字段:Bucket中的“h”用于存儲原始key;HashTable中的nTableMask是一個掩碼,一般被設為nTableSize – 1,與哈希算法有密切關系,后面討論哈希算法時會詳述;arBuckets指向一個指針數(shù)組,其中每個元素是一個指向Bucket鏈表的頭指針。

哈希算法:PHP哈希表最小容量是8(2^3),最大容量是0×80000000(2^31),并向2的整數(shù)次冪圓整(即長度會自動擴展為2的整數(shù)次冪,如13個元素的哈希表長度為16;100個元素的哈希表長度為128)。nTableMask被初始化為哈希表長度(圓整后)減1。具體代碼在zend/Zend_hash.c的_zend_hash_init函數(shù)中,這里截取與本文相關的部分并加上少量注釋。

ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC){ uint i = 3; Bucket **tmp; SET_INCONSISTENT(HT_OK); //長度向2的整數(shù)次冪圓整 if (nSize >= 0x80000000) {/* prevent overflow */ht->nTableSize = 0x80000000; } else {while ((1U << i) < nSize) { i++;}ht->nTableSize = 1 << i; } ht->nTableMask = ht->nTableSize - 1; /*此處省略若干代碼…*/ return SUCCESS;}

值得一提的是PHP向2的整數(shù)次冪取圓整方法非常巧妙,可以背下來在需要的時候使用。

Zend HashTable的哈希算法很簡單:hash(key)=key&nTableMask

即簡單將數(shù)據(jù)的原始key與HashTable的nTableMask進行按位與即可。如果原始key為字符串,則首先使用Times33算法將字符串轉(zhuǎn)為整形再與nTableMask按位與:hash(strkey)=time33(strkey)&nTableMask

下面是Zend源碼中查找哈希表的代碼:

ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData){ uint nIndex; Bucket *p; IS_CONSISTENT(ht); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) {if ((p->h == h) && (p->nKeyLength == 0)) { *pData = p->pData; return SUCCESS;}p = p->pNext; } return FAILURE;}ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData){ ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); h = zend_inline_hash_func(arKey, nKeyLength); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) {if ((p->h == h) && (p->nKeyLength == nKeyLength)) { if (!memcmp(p->arKey, arKey, nKeyLength)) {*pData = p->pData;return SUCCESS; }}p = p->pNext; } return FAILURE;}

其中zend_hash_index_find用于查找整數(shù)key的情況,zend_hash_find用于查找字符串key。邏輯基本一致,只是字符串key會通過zend_inline_hash_func轉(zhuǎn)為整數(shù)key,zend_inline_hash_func封裝了times33算法,具體代碼就不貼出了。

攻擊

知道了PHP內(nèi)部哈希表的算法,就可以利用其原理構(gòu)造用于攻擊的數(shù)據(jù)。一種最簡單的方法是利用掩碼規(guī)律制造碰撞。上文提到Zend HashTable的長度nTableSize會被圓整為2的整數(shù)次冪,假設我們構(gòu)造一個2^16的哈希表,則nTableSize的二進制表示為:1 0000 0000 0000 0000,而nTableMask = nTableSize – 1為:0 1111 1111 1111 1111。接下來,可以以0為初始值,以2^16為步長,制造足夠多的數(shù)據(jù),可以得到如下推測:

0000 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 00001 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 00010 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 00011 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 00100 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0……

概況來說只要保證后16位均為0,則與掩碼位于后得到的哈希值全部碰撞在位置0。下面是利用這個原理寫的一段攻擊代碼:

<?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';?>

這段代碼在我的VPS上(單CPU,512M內(nèi)存)上用了近88秒才完成,并且在此期間CPU資源幾乎被用盡。

而普通的同樣大小的哈希表插入僅用時0.036秒:

<?php$size = pow(2, 16);$startTime = microtime(true);$array = array();for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $size; $key += 1) { $array[$key] = 0;}$endTime = microtime(true);echo $endTime - $startTime, ’ seconds’, 'n';?>

可以證明第二段代碼插入N個元素的時間在O(N)水平,而第一段攻擊代碼則需O(N^2)的時間去插入N個元素。

當然,一般情況下很難遇到攻擊者可以直接修改PHP代碼的情況,但是攻擊者仍可以通過一些方法間接構(gòu)造哈希表來進行攻擊。例如PHP會將接收到的HTTP POST請求中的數(shù)據(jù)構(gòu)造為$_POST,而這是一個Array,內(nèi)部就是通過Zend HashTable表示,因此攻擊者只要構(gòu)造一個含有大量碰撞key的post請求,就可以達到攻擊的目的。具體做法不再演示。

防御

POST攻擊的防護:針對POST方式的哈希碰撞攻擊,目前PHP的防護措施是控制POST數(shù)據(jù)的數(shù)量。在>=PHP5.3.9的版本中增加了一個配置項max_input_vars,用于標識一次http請求最大接收的參數(shù)個數(shù),默認為1000。因此PHP5.3.x的用戶可以通過升級至5.3.9來避免哈希碰撞攻擊。5.2.x的用戶可以使用這個patch:http://www.laruence.com/2011/12/30/2440.html。

另外的防護方法是在Web服務器層面進行處理,例如限制http請求body的大小和參數(shù)的數(shù)量等,這個是現(xiàn)在用的最多的臨時處理方案。具體做法與不同Web服務器相關,不再詳述。

上面的防護方法只是限制POST數(shù)據(jù)的數(shù)量,而不能徹底解決這個問題。例如,如果某個POST字段是一個json數(shù)據(jù)類型,會被PHP json_decode,那么只要構(gòu)造一個超大的json攻擊數(shù)據(jù)照樣可以達到攻擊目的。理論上,只要PHP代碼中某處構(gòu)造Array的數(shù)據(jù)依賴于外部輸入,則都可能造成這個問題,因此徹底的解決方案要從Zend底層HashTable的實現(xiàn)動手。一般來說有兩種方式,一是限制每個桶鏈表的最長長度;二是使用其它數(shù)據(jù)結(jié)構(gòu)如紅黑樹取代鏈表組織碰撞哈希(并不解決哈希碰撞,只是減輕攻擊影響,將N個數(shù)據(jù)的操作時間從O(N^2)降至O(NlogN),代價是普通情況下接近O(1)的操作均變?yōu)镺(logN))。

目前使用最多的仍然是POST數(shù)據(jù)攻擊,因此建議生產(chǎn)環(huán)境的PHP均進行升級或打補丁。至于從數(shù)據(jù)結(jié)構(gòu)層面修復這個問題,目前還沒有任何方面的消息。

標簽: PHP
相關文章:
主站蜘蛛池模板: 日韩高清在线二区 | 欧美一级专区免费大片野外交 | 久久精品国产欧美日韩亚洲 | 免费一级毛片在线播放放视频 | a毛片毛费观看 | 国产综合13p | 国产精品久久久久久一区二区三区 | 亚洲成年人网址 | 亚洲欧美偷拍自拍 | 香港全黄一级毛片在线播放 | 毛片在线不卡 | 91久久精品国产免费一区 | 日本aa毛片a级毛片免费观看 | 精品videosex性欧美 | 中文精品视频一区二区在线观看 | 亚洲成人在线免费视频 | 久久伊人免费视频 | 热re66久久精品国产99热 | 6080伦理久久亚洲精品 | 91九色精品国产免费 | 国产成人精品免费 | videos性欧美 | 亚洲最新视频在线观看 | 免费一级毛片不卡在线播放 | 性午夜 | 日本一区二区免费在线观看 | 特别福利视频在线观看 | 六月丁香久久丫 | 国产亚洲欧美一区二区三区 | 日韩在线 中文字幕 | 精品国产精品a | 欧美毛片a级毛片免费观 | 香蕉超级碰碰碰97视频蜜芽 | 国内精品久久久久久 | 欧美色成人tv在线播放 | www.黄com| 日韩亚洲一区二区三区 | 碰碰碰免费公开在线视频 | 草久久免费视频 | 国产大片免费天天看 | 久久视频免费 |