国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

PHP哈希表碰撞攻擊原理

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

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

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

哈希表是一種查找效率極高的數據結構,很多語言都在內部實現了哈希表。PHP中的哈希表是一種極為重要的數據結構,不但用于表示Array數據類型,還在Zend虛擬機內部用于存儲上下文環境信息(執行上下文的變量及函數均使用哈希表結構存儲)。

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

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

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

 

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

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

Zend哈希表的內部實現

數據結構

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

  1. typedef struct bucket { 
  2. ulong h;                        /* Used for numeric indexing */ 
  3.    uint nKeyLength; 
  4.    void *pData; 
  5.    void *pDataPtr; 
  6.    struct bucket *pListNext; 
  7.    struct bucket *pListLast; 
  8.    struct bucket *pNext; 
  9.    struct bucket *pLast; 
  10.    char arKey[1]; /* Must be last element */ 
  11. } Bucket; 
  12.  
  13. typedef struct _hashtable { 
  14.    uint nTableSize; 
  15.    uint nTableMask; 
  16.    uint nNumOfElements; 
  17.    ulong nNextFreeElement; 
  18.    Bucket *pInternalPointer;   /* Used for element traversal */ 
  19.    Bucket *pListHead; 
  20.    Bucket *pListTail; 
  21.    Bucket **arBuckets; 
  22.    dtor_func_t pDestructor; 
  23.    zend_bool persistent; 
  24.    unsigned char nApplyCount; 
  25.    zend_bool bApplyProtection; 
  26. #if ZEND_DEBUG 
  27.    int inconsistent; 
  28. #endif 
  29.  
  30. } HashTable;  

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

哈希算法

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

  1. 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) 
  2.    uint i = 3; 
  3.    Bucket **tmp; 
  4.  
  5.    SET_INCONSISTENT(HT_OK); 
  6.  
  7.    //長度向2的整數次冪圓整 
  8.    if (nSize >= 0x80000000) { 
  9.        /* prevent overflow */ 
  10.        ht->nTableSize = 0x80000000; 
  11.    } else { 
  12.        while ((1U << i) < nSize) { 
  13.            i++; 
  14.        } 
  15.        ht->nTableSize = 1 << i; 
  16.    } 
  17.  
  18.    ht->nTableMask = ht->nTableSize - 1; 
  19.  
  20.    /*此處省略若干代碼…*/ 
  21.  
  22.    return SUCCESS; 
  23.  
  24.  

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

Zend HashTable的哈希算法異常簡單: 

 

即簡單將數據的原始key與HashTable的nTableMask進行按位與即可。

如果原始key為字符串,則首先使用Times33算法將字符串轉為整形再與nTableMask按位與。 

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

  1. ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData) 
  2.    uint nIndex; 
  3.    Bucket *p; 
  4.  
  5.    IS_CONSISTENT(ht); 
  6.  
  7.    nIndex = h & ht->nTableMask; 
  8.  
  9.    p = ht->arBuckets[nIndex]; 
  10.    while (p != NULL) { 
  11.        if ((p->h == h) && (p->nKeyLength == 0)) { 
  12.            *pData = p->pData; 
  13.            return SUCCESS; 
  14.        } 
  15.        p = p->pNext; 
  16.    } 
  17.    return FAILURE; 
  18.  
  19. ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData) 
  20.    ulong h; 
  21.    uint nIndex; 
  22.    Bucket *p; 
  23.  
  24.    IS_CONSISTENT(ht); 
  25.  
  26.    h = zend_inline_hash_func(arKey, nKeyLength); 
  27.    nIndex = h & ht->nTableMask; 
  28.  
  29.    p = ht->arBuckets[nIndex]; 
  30.    while (p != NULL) { 
  31.        if ((p->h == h) && (p->nKeyLength == nKeyLength)) { 
  32.            if (!memcmp(p->arKey, arKey, nKeyLength)) { 
  33.                *pData = p->pData; 
  34.                return SUCCESS; 
  35.            } 
  36.        } 
  37.        p = p->pNext; 
  38.    } 
  39.    return FAILURE; 
  40.  

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

攻擊

基本攻擊

知道了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。

下面是利用這個原理寫的一段攻擊代碼:

  1. <?php 
  2.  
  3. $size = pow(2, 16); 
  4.  
  5. $startTime = microtime(true); 
  6.  
  7. $array = array(); 
  8. for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) { 
  9.    $array[$key] = 0; 
  10.  
  11. $endTime = microtime(true); 
  12.  
  13. echo $endTime - $startTime, ' seconds'"\n" 

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

    

 

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

  1. <?php 
  2.  
  3.  
  4. $size = pow(2, 16); 
  5.  
  6. $startTime = microtime(true); 
  7.  
  8. $array = array(); 
  9. for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $size; $key += 1) { 
  10.    $array[$key] = 0; 
  11.  
  12. $endTime = microtime(true); 
  13.  
  14. echo $endTime - $startTime, ' seconds'"\n" 

 

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

POST攻擊

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

防護

POST攻擊的防護

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

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

其它防護

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

目前使用最多的仍然是POST數據攻擊,因此建議生產環境的PHP均進行升級或打補丁。至于從數據結構層面修復這個問題,目前還沒有任何方面的消息。

參考

[1] Supercolliding a PHP array

[2] PHP5.2.*防止Hash沖突拒絕服務攻擊的Patch

[3] 通過構造Hash沖突實現各種語言的拒絕服務攻擊

[4] PHP數組的Hash沖突實例

[5] PHP 5.4.0 RC4 released 

責任編輯:龐桂玉 來源: 程序源
相關推薦

2011-12-30 15:20:29

2020-10-16 11:41:07

攻擊

2017-07-27 14:21:40

phpPHP源碼分析hashtable

2016-12-21 10:35:55

PHP內核PHP哈希表

2010-09-26 17:13:31

2014-04-15 11:22:24

2021-09-03 07:23:59

哈希洪水攻擊黑客DDoS

2021-04-29 10:08:10

數據結構哈希表

2012-01-06 09:07:51

2017-10-12 15:41:45

2017-01-19 09:24:04

2010-07-16 13:10:36

Perl哈希表

2019-05-30 10:15:30

2010-05-21 14:53:33

2013-07-26 14:59:13

2011-07-12 10:38:10

2024-10-16 11:03:30

Linux高性能編程

2023-11-24 17:58:03

Python哈希

2010-07-13 16:34:34

Perl 哈希表

2009-12-10 09:39:52

點贊
收藏

51CTO技術棧公眾號

美女久久久久| 日本一区二区在线视频观看| 日本三级免费网站| 91精品国产成人观看| 欧美片在线播放| 高清一区在线观看| 五月婷婷六月综合| 欧美激情视频免费观看| 深夜福利在线观看直播| 99久久综合国产精品| 国产美女精品久久久| **日韩最新| 亚洲第一中文字幕| 美女视频免费观看网站在线| 国产成人精品综合在线观看 | 亚洲女人天堂成人av在线| 黄色三级电影网| 精品一区二区三区久久久| 亚洲综合在线中文字幕| 日本精品视频| 日韩精品在线电影| 成人在线观看免费网站| 亚洲国产一二三| 国产a级片免费观看| 不卡一区在线观看| 日本中文不卡| 免费成人av在线| 午夜免费电影一区在线观看| 亚洲精品91| 92看片淫黄大片看国产片| 白嫩亚洲一区二区三区| 日韩毛片在线看| 成人免费短视频| 久久精品国产亚洲| 精品一区二区三区四区五区| 亚洲欧美另类中文字幕| 成人污版视频| 欧美激情aaaa| 欧美aaaa视频| 麻豆av一区二区| 国产精品白丝jk黑袜喷水| 成人短视频在线观看免费| 国产精品久久久久久久久果冻传媒| 97在线资源| 亚洲精美色品网站| 天堂久久av| 国产一区深夜福利| 日韩av一区二区在线影视| 日本不卡高清视频一区| 日韩国产高清在线| 国产美女三级视频| 夜夜爽夜夜爽精品视频| 国产网站在线免费观看| 国产亚洲欧洲高清一区| 亚洲国产欧美日韩在线观看第一区 | 成人综合影院| 日韩av网站电影| 亚洲制服欧美另类| 久久久久免费网| 久久免费午夜影院| 毛片在线视频| 国产精品99久久久久久www| 国产一区日韩一区| 少妇性l交大片| 日韩一区二区三区高清免费看看| 日本久久二区| 日本中文不卡| 午夜一区二区三区视频| av第一福利在线导航| 91亚洲精品在线观看| 国产亚洲1区2区3区| av资源中文在线天堂| 91久久偷偷做嫩草影院| 国产色91在线| 范冰冰一级做a爰片久久毛片| 91久久精品www人人做人人爽| 国产精品久久久久一区二区三区 | 欧美vide| 性欧美亚洲xxxx乳在线观看| 可以看av的网站久久看| 黄色网址在线播放| 久久久亚洲精选| av在线播放成人| av大大超碰在线| 国产成人成网站在线播放青青| 国产精品欧美一区喷水| 伊人久久一区| 伊人资源视频在线| 欧美成人激情视频| 日本一二三四高清不卡| 北条麻妃一区二区三区在线| 黄色永久免费网站| 日本精品一区二区三区在线播放视频| 久久免费黄色| 四虎永久在线高清国产精品| 亚洲一区二区久久久| 亚洲精选一区| 青青草免费在线视频| 亚洲欧美日韩中文视频| 亚洲高清资源| 手机看片一级片| 亚洲高清免费观看高清完整版| 日本中文字幕一区二区视频| 国产精品久久在线观看| 三级不卡在线观看| 丰满少妇在线观看| 欧美日本免费一区二区三区| 精品国产麻豆| 欧美二区三区| av在线综合网| 国产原创av在线| 久久精品人人爽| 亚洲视频中文| 窝窝九色成人影院| 日韩成人在线播放| 欧美激情理论| 啊啊啊国产视频| 日韩成人久久久| 欧美日韩一区自拍| 成人满18在线观看网站免费| 日韩精品久久久久| 在线电影一区| 色偷偷免费视频| 91精品国产乱码| 日本欧美加勒比视频| 亚州一区二区| 日本二区视频| 国产欧美精品一区二区三区-老狼| 亚洲女爱视频在线| 成人看的羞羞网站| 黄色成人在线| 很污的网站在线观看| 91久久精品视频| 国产精品12| 国产精品区一区二区三| 国产高清中文字幕在线| 96久久精品| 欧美 亚欧 日韩视频在线| 你懂的在线观看| 在线观看成人免费| …久久精品99久久香蕉国产| 亚洲一区二三区| 欧美日韩四区| 久久久加勒比| 毛片免费在线观看| 可以看毛片的网址| 国产日韩一区欧美| 欧美不卡视频一区发布| 亚洲午夜在线观看视频在线| 麻豆精品一区二区av白丝在线| 午夜电影一区| 国产一区一区| 一个人免费观看视频www在线播放| 色噜噜色狠狠狠狠狠综合色一| 欧美日韩黄色影视| 美女少妇全过程你懂的久久| 五月天电影免费在线观看一区| 亚洲综合精品一区二区| 亚洲欧美中文日韩在线v日本| 亚洲国产美女搞黄色| 国产成人精品三级| 国产精品试看| 中文字幕一区二区三区欧美日韩| 成人小视频在线观看| 日韩和的一区二区| 丝袜美腿一区二区三区动态图| 在线网址91| 黄色网页在线免费看| 成人在线视频成人| 可以免费看污视频的网站在线| 最新天堂中文在线| 成人一区二区免费视频| 蜜桃久久影院| 久久精品日产第一区二区三区精品版| 国产精品丝袜一区二区三区| 色妞一区二区三区| 久久久国产一区| 久久成人在线视频| 极品校花啪啪激情久久| 成人97在线观看视频| 亚洲午夜久久久久久久久电影网 | 久久一留热品黄| 四虎影视成人精品国库在线观看| 国产午夜福利100集发布| 欧美成人亚洲成人| 亚洲欧美影音先锋| 亚洲韩国在线| 国产欧美亚洲日本| 欧美日韩电影一区二区三区| 精品欧美一区二区精品久久| www日韩av| 国产精品久久久久久久av大片| 96精品视频在线| 欧美一级电影在线| 国产精品av免费在线观看| 成人高清免费| 激情文学一区| 欧美精品xxxxbbbb| 国产女人18毛片水真多成人如厕| 精品一区精品二区|