调查HashDoS问题
現(xiàn)在是時候更深入地研究復(fù)雜性攻擊并查看來源了。 我完全假設(shè)java.util.HashMap和java.util.Hashtable是受此攻擊影響的最常用的Java數(shù)據(jù)結(jié)構(gòu),因此本文僅將代碼集中在這些類型的后面。
哈希函數(shù)和索引數(shù)據(jù)結(jié)構(gòu)的簡要介紹
哈希索引數(shù)據(jù)結(jié)構(gòu)因其簡單的用法和優(yōu)點而非常受歡迎:
- 無需打擾索引表即可找到所需數(shù)據(jù)的正確位置
 - 通過使用關(guān)鍵字而不是索引號訪問數(shù)據(jù)
 - 添加或刪除操作的時間幾乎恒定
 
為了獲得這些好處,哈希索引數(shù)據(jù)結(jié)構(gòu)遵循有關(guān)如何對數(shù)據(jù)進行索引的聰明思想。 索引是通過散列與背后數(shù)據(jù)關(guān)聯(lián)的關(guān)鍵字來計算的。 考慮以下示例,這是一個類似于代碼的簡單示例:
myHashIndexedDataStructure [hash(keyword)] =特定數(shù)據(jù)聽起來很完美,但是它有一個主要缺點:在大多數(shù)情況下,使用的哈希函數(shù)不是加密函數(shù)。
根據(jù)Wikipedia的說法,函數(shù)自行調(diào)用哈希函數(shù)的唯一強制特征是
“將可變長度的大型數(shù)據(jù)集(稱為鍵)映射到固定長度的較小數(shù)據(jù)集”與稱自己為密碼哈希函數(shù)(再次是來自Wikipedia的定義)相反,它必須滿足更多,甚至更強大的要求:
”
- 計算任何給定消息的哈希值很容易(但不一定很快)
 - 生成具有給定哈希值的消息是不可行的
 - 在不更改哈希值的情況下修改消息是不可行的
 - 找到兩個具有相同哈希值的不同消息是不可行的
 
 ” 
 長話短說,讓我們總結(jié)一下我們學(xué)到的知識以及用這些知識得出的結(jié)論: 
如果關(guān)鍵字沖突,則哈希索引數(shù)據(jù)結(jié)構(gòu)需要某種計劃b)–一種后備算法–關(guān)于如何處理具有相同關(guān)鍵字哈希值的多個數(shù)據(jù)集。
實際上,有幾種可行的方法:
- 探測(轉(zhuǎn)移到固定或可計算的間隔)
 - 多重哈希
 - 條目鏈接(沖突條目的構(gòu)建列表)
 - 覆蓋現(xiàn)有條目
 
以下哪種策略需要Java? 首先,我們將檢查java.util的代碼。 Hashtable (僅顯示有趣的部分,為清晰起見,其余代碼被省略了:
public synchronized V put(K key, V value) { ... // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) %tab.length; for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } } ... // Creates the new entry. Entry<K,V> e = tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; return null; } 可以看出,此類使用鍵對象( 關(guān)鍵字 )的hashCode ()函數(shù)來計算哈希值。 它遵循ANDing(&運算符),為了將其正確表示為Integer,MODULO(%運算符),將表大小(建立循環(huán)環(huán)結(jié)構(gòu):(table.length + 1)mod table.length?1,除以余數(shù))始終解決標簽 []中的條目。 
 此后,將考慮所有條目 (-ies)并檢查哈希值是否相同以及對象本身是否相同。 if -clause防止存儲同一對象的多個實例–舊的實例僅由新的實例替換。 
 如果在key.hashCode ()標識的當(dāng)前位置上找不到相同的對象(關(guān)于哈希值和equals ()方法),則將創(chuàng)建一個新的Entry,將其放置在當(dāng)前位置并在該位置處理舊的Entry對象。 
 到目前為止,看起來java.util.Hashtable在每個tab []之后都使用某種列表作為數(shù)據(jù)結(jié)構(gòu)。 
查看私有內(nèi)部類java.util.Hashtable.Entry <K,V>的代碼時,可以確認此假設(shè)。
private static class Entry<K,V> implements Map.Entry<K,V> { int hash; K key; V value; Entry<K,V> next; 下一個Entry對象僅指向下一個Entry 。 這代表一個定制的鏈表。 
 java.util.HashMap的代碼更加復(fù)雜,并且表現(xiàn)部分不同(允許使用null值,!不同步!),但是基于相同的思想。 在這里調(diào)查代碼不會發(fā)現(xiàn)任何新內(nèi)容,除了Entry重新被重新實現(xiàn)的事實…)。 
兩種實現(xiàn)都依賴于哈希索引數(shù)組的每個條目后面的鏈接列表。
進攻思路
 現(xiàn)在我們知道了java.util.Hashtable和java.util.HashMap背后的實現(xiàn)細節(jié),我們可以回到稱為HashDoS的攻擊。 該攻擊實現(xiàn)了Crosby,SA,Wallach,DS的想法: 通過算法復(fù)雜性攻擊拒絕服務(wù)。 在:第十二屆USENIX安全研討會的會議記錄–第12卷,USENIX協(xié)會(2003) 
 總結(jié)一下:散列索引的數(shù)據(jù)結(jié)構(gòu)可能會因引發(fā)不利的狀態(tài)而大大減慢速度。 理想的哈希索引數(shù)據(jù)結(jié)構(gòu)如下所示: 
 在這種情況下,使用具有不同哈希值的關(guān)鍵字更改,刪除或添加數(shù)據(jù)的時間幾乎是恒定的。 通過使用關(guān)鍵字的哈希值作為索引,可以輕松找到位置,并且無需迭代列表即可立即顯示數(shù)據(jù)。 
 讓我們看一下哈希索引數(shù)據(jù)結(jié)構(gòu)的另一種不利狀態(tài): 
像這樣的結(jié)構(gòu),CRUD操作的恒定時間已經(jīng)結(jié)束了……
這會大大減慢處理線程的速度。 一個非常快的數(shù)據(jù)結(jié)構(gòu)已變成一個鏈表,并帶有額外的開銷(計算哈希值)。 散列索引數(shù)據(jù)結(jié)構(gòu)的所有好處都將被抹去。 好像還不夠糟糕,大多數(shù)哈希索引數(shù)據(jù)結(jié)構(gòu)都啟用了稱為重新哈希的功能。 當(dāng)數(shù)據(jù)結(jié)構(gòu)超過定義的負載(例如,在Java中為75%)時,出于優(yōu)化原因,將重新整理表。 大多數(shù)情況下,絕對希望使用此功能,但在這種特殊情況下,它甚至?xí)p慢整個過程。
利用問題
要利用此行為,必須計算出一大堆沖突關(guān)鍵字。 例如,如果我們假設(shè)關(guān)鍵字的類型為java.lang.String ,我們可以看一下其hashCode ()函數(shù):
public int hashCode() { int h = hash; if (h == 0) { int off = offset; char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++]; } hash = h; } return h; } 這似乎是DJ Bernstein設(shè)計的功能DJBX33A的自定義版本,可以很容易地發(fā)現(xiàn)沖突。 
 該函數(shù)具有一個有趣的屬性,將在以下示例中進行演示: 
我們看到碰撞值的串聯(lián)再次導(dǎo)致碰撞值。 我們可以繼續(xù)做下去,并獲得大量碰撞關(guān)鍵字。 這使查找沖突比單純的暴力破解更加容易。
 我們針對本地Web服務(wù)對此進行了測試,并且可以通過使用沖突關(guān)鍵字作為標記屬性來顯著降低正在運行的Web應(yīng)用程序服務(wù)器的速度。 
 我不確定是否真的可能使計算機崩潰,或者是否存在某種非顯而易見的機制來防止服務(wù)器自行殺死(我們尚未在服務(wù)器端研究處理代碼),但是可以肯定地阻止服務(wù)器在可接受的時間內(nèi)正常運行。 對Web服務(wù)的請求很容易被延遲。 
也許我會在不久的將來付出一些努力來收集測量數(shù)據(jù)(#colliding keys –系統(tǒng)響應(yīng)時間)。 如果我這樣做,您將在此博客上找到數(shù)據(jù)…
帶你去的拐角點
- 永遠不要只依賴hashCode() –容易出錯
 -  避免像 
if(password.hashCode() == referencePassword.hashCode()) {
} else { - 在決定/反對數(shù)據(jù)類型/結(jié)構(gòu)時,花幾秒鐘的時間在實現(xiàn)細節(jié)上
 - 篩選傳入的數(shù)據(jù)–裁剪其大小,拒絕超長參數(shù)等。
 - 小心,并始終注意編碼最佳實踐!
 
進一步有趣的觀點
 在此示例中,我們使用java.lang.String作為關(guān)鍵字對象。 有趣的是還可以使用什么,以及在JRE代碼或大量使用的項目中,沖突的哈希值在何處用于數(shù)據(jù)結(jié)構(gòu)或什至更糟糕的目的。 
 可以看看Object.hashCode ()是如何實現(xiàn)的(它是本機代碼)–這將是一個不錯的目標,因為所有其他對象都擴展了該基類。 如果擴展類沒有覆蓋hashCode ()函數(shù),而是依賴于正確的,無沖突的輸出,則這對于更復(fù)雜的攻擊可能很有用。 考慮一下如果序列化依賴于相應(yīng)的代碼會發(fā)生什么……。 
如果有人已經(jīng)知道一些脆弱的地方,請告訴我們! 我們非常有興趣,但是由于時間有限,無法達到我們想要的深度。
謝謝
我要再次感謝Juraj Somorovsky所做的豐富的聯(lián)合研究工作! 此外,我們還要感謝oCERT團隊的Andrea Barisani和紅帽安全響應(yīng)團隊的 Vincent Danen ,他們與我們討論了這個問題!
參考:從我們的JCG合作伙伴處 調(diào)查HashDoS問題 ? Java安全和相關(guān)主題博客中的Christopher Meyer。
翻譯自: https://www.javacodegeeks.com/2012/02/investigating-hashdos-issue.html
總結(jié)
以上是生活随笔為你收集整理的调查HashDoS问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 公司网络发起ddos攻击(公司网络发起d
 - 下一篇: 出入境备案撤销需几天时间(出入境备案撤销