Padding Oracle攻擊實(shí)例分析
在《2010年揚(yáng)名的十大WEB黑客技術(shù)》和《淺談ASP.NET的Padding Oracle攻擊》中,我們都提到了Padding Oracle Attack的相關(guān)內(nèi)容,也對其進(jìn)行了一些簡單的了解。接下來,通過這篇譯文,我們再來對其進(jìn)行一下深入的學(xué)習(xí)吧。
最近出現(xiàn)了許多有關(guān)Padding Oracle Attack的聲音,在今年夏天早些時候的BlakHat Europe會議上,Juliano Rizzo和Thai Duong在他們的演講中演示了這種攻擊方式。雖然Padding Oracle是種相對容易的攻擊方式,但如果您還沒有對它的自動攻擊原理有一定了解,那么利用它進(jìn)行攻擊還是需要不少時間的。由于缺少好用的工具以識別及利用Padding Oracles,我們開發(fā)了一個基于Padding Oracle的內(nèi)部腳本,PadBuster,現(xiàn)在我們打算將它與社區(qū)分享。您可以在這里下載工具,現(xiàn)在我們也會花些時間來討論這個工具的工作方式,以及它所支持的幾種場景。
一些背景知識
在討論P(yáng)adBuster之前,我們先來簡單討論一下典型的Padding Oracle Attack基礎(chǔ)。故名思義,Padding Oracle Attack背后的關(guān)鍵性概念便是加/解密時的填充(Padding)。明文信息可以是任意長度,但是塊狀加密算法需要所有的信息都由一定數(shù)量的數(shù)據(jù)塊組成。為了滿足這樣的需求,便需要對明文進(jìn)行填充,這樣便可以將它分割為完整的數(shù)據(jù)塊。
加密時可以使用多種填充規(guī)則,但最常見的填充方式之一是在PKCS#5標(biāo)準(zhǔn)中定義的規(guī)則。PCKS#5的填充方式為:明文的最后一個數(shù)據(jù)塊包含N個字節(jié)的填充數(shù)據(jù)(N取決于明文最后一塊的數(shù)據(jù)長度)。下圖是一些示例,展示了不同長度的單詞(FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT)以及它們使用PKCS#5填充后的結(jié)果(每個數(shù)據(jù)塊為8字節(jié)長)。
請注意,每個字符串都至少有1個字節(jié)的填充數(shù)據(jù),因此7字節(jié)的值(如AVOCADO)則使用0x01進(jìn)行填充,而8字節(jié)的值(如PLANTAIN)則會填充一個額外的數(shù)據(jù)塊。填充字節(jié)的值也說明了填充的字節(jié)數(shù),因此待加密數(shù)據(jù)的最后幾個字節(jié)必須是以下幾種情況之一:
如果解密后的最后一個數(shù)據(jù)塊末尾并非這些合法的字節(jié)序列,大部分加/解密程序都會拋出一個填充異常。這個異常對于攻擊者尤為關(guān)鍵,它是Padding Oracle Attack的基礎(chǔ)。#p#
一個基本的Padding Oracle Attack場景
作為一個具體例子,請考慮以下場景:
http://sampleapp/home.jsp?UID=7B216A634951170FF851D6CC68FC9537858795A28ED4AAC6
某個應(yīng)用程序使用Query String參數(shù)來傳遞一個用戶加密后的用戶名,公司ID及角色I(xiàn)D。參數(shù)使用CBC模式加密,每次都使用不同的初始化向量(IV,Initialization Vector)并添加在密文前段。
當(dāng)應(yīng)用程序接受到加密后的值以后,它將返回三種情況:
接受到正確的密文之后(填充正確且包含合法的值),應(yīng)用程序正常返回(200 - OK)。
接受到非法的密文之后(解密后發(fā)現(xiàn)填充不正確),應(yīng)用程序拋出一個解密異常(500 - Internal Server Error)。
接受到合法的密文(填充正確)但解密后得到一個非法的值,應(yīng)用程序顯示自定義錯誤消息(200 - OK)。
上述的場景體現(xiàn)了一個典型的Padding Oracle(填充提示),我們可以利用應(yīng)用程序的行為輕易了解某個加密的值是否填充正確。這里的單詞Oracle代表了一種機(jī)制,用于了解某個測試是否通過。
既然已經(jīng)給出了場景,那么我們便來查看應(yīng)用程序所使用的一個加密后的參數(shù)。這個參數(shù)保存了使用分號隔離的一系列值,在我們的示例中,則是用戶名(BRIAN),公司ID(12)及角色I(xiàn)D(12):因此這里的明文是“BRIAN;12;2;”。以下則是經(jīng)過加密的Query String實(shí)例,請注意加密后的UID參數(shù)使用了ASCII十六進(jìn)制表示法。
在實(shí)際情況中,攻擊者并不會知道這里所對應(yīng)的明文是多少,不過作為示例,我們已經(jīng)知道了明文、填充、以及加密后的值(如下表)。正如之前所提到的那樣,IV添加在密文的前段,即最前面8個字節(jié)。
攻擊者可以根據(jù)加密后值的長度來推測出數(shù)據(jù)塊的大小。由于長度(這里是24)能被8整除但不能被16整除,因此可以得知數(shù)據(jù)塊的大小是8個字節(jié)。現(xiàn)在我們來觀察下加密和解密的內(nèi)部實(shí)現(xiàn),下圖便展示了字節(jié)級別的運(yùn)算方式,這對以后攻擊方式的討論很有幫助。請注意,其中帶圓圈的加號表示XOR(異或)操作。
加密過程:
解密過程:
同樣值得指出的是,解密之后的最后一個數(shù)據(jù)塊,其結(jié)尾應(yīng)該包含正確的填充序列。如果這點(diǎn)沒有滿足,那么加/解密程序就會拋出一個填充異常。#p#
利用Padding Oracle進(jìn)行解密
我們現(xiàn)在來關(guān)注一下如何利用Padding Oracle Attack進(jìn)行解密。我們將每次操作一個單獨(dú)的加密塊,因此我們可以獨(dú)立出第一塊密文(IV后的那塊),在前面加上全為NULL的IV值,并發(fā)送至應(yīng)用程序。以下是URL極其相關(guān)回復(fù):
Request: http://sampleapp/home.jsp?UID=0000000000000000F851D6CC68FC9537 Response: 500 - Internal Server Error
回復(fù)的500錯誤是意料之中的,因為這個值在解密后完全非法。下圖展示了應(yīng)用程序在嘗試解密的時候究竟做了哪些事情。您會發(fā)現(xiàn),因為我們只處理單個數(shù)據(jù)塊,因此它的結(jié)尾必須包含正確的填充字節(jié),才能避免出現(xiàn)非法填充異常。
如上圖所示,在解密之后,數(shù)據(jù)塊的末尾并沒有包含正確的填充序列,因此出現(xiàn)了異常。現(xiàn)在我們將IV加一,并發(fā)送同樣的密文,看看會發(fā)生什么:
Request: http://sampleapp/home.jsp?UID=0000000000000001F851D6CC68FC9537 Response: 500 - Internal Server Error
與之前一樣,我們得到了500異常。這是因為在解密后我們還是沒有獲得合法的填充序列。稍有不同的是,我們在深入內(nèi)部之后會發(fā)現(xiàn),最后一個字節(jié)的值會有所變化(變成了0x3C而不是0x3D)。
如果我們重復(fù)發(fā)送這樣的請求,每次將IV的最后一個字節(jié)加一(直至0xFF),那么最終我們將會產(chǎn)生一個合法的單字節(jié)填充序列(0x01)。對于可能的256個值中,只有一個值會產(chǎn)生正確的填充字節(jié)0x01。遇上這個值的時候,你應(yīng)該得到一個不同于其他255個請求的回復(fù)結(jié)果:
Request: http://sampleapp/home.jsp?UID=000000000000003CF851D6CC68FC9537 Response: 200 OK
同樣,我們從示意圖中了解一下此時發(fā)生了什么:
在這個情況下,我們便可以推斷出中間值(Intermediary Value)的最后一個字節(jié),因為我們知道它和0x3C異或后的結(jié)果為0x01,于是:
因為 [Intermediary Byte] ^ 0×3C == 0×01,
得到 [Intermediary Byte] == 0×3C ^ 0×01,
所以 [Intermediary Byte] == 0×3D
現(xiàn)在我們可以更進(jìn)一步。我們已經(jīng)知道了中間值的最后一個字節(jié),于是我們可以推斷出解密后的值是多少。您可以回憶一下,在解密的過程中,中間值的每個字節(jié)都會與密文中的前一個數(shù)據(jù)塊(對于第一個數(shù)據(jù)塊來說便是IV)的對應(yīng)字節(jié)進(jìn)行異或操作,于是我們使用之前示例中原來的IV中的最后一個字節(jié)(0x0F),與中間值異或一下便可以得到明文。不出意料,我們會得到0x32,這表示數(shù)字“2”(明文中第一個數(shù)據(jù)塊的最后一個字節(jié))。
我們現(xiàn)在已經(jīng)破解了示例數(shù)據(jù)塊中的第8個字節(jié),是時候關(guān)注第7個字節(jié)了。在破解第8個字節(jié)時,我們使用暴力枚舉IV,讓解密后的最后一個字節(jié)成為0x01(合法填充)。在破解第7個字節(jié)的時候,我們要做的事情也差不多,不過此時要求第7個字節(jié)與第8個字節(jié)都為0x02(再重復(fù)一遍,這表示合法的填充)。我們已經(jīng)知道,中間值的最后一個字節(jié)是0x3D,因此我們可以將IV中的第8個字節(jié)設(shè)為0x3F(這會產(chǎn)生0x02)并暴力枚舉IV的第七個字節(jié)(從0x00開始,直至0xFF)。
我們再次遭遇填充異常,直至遇上某個值,它使得解密后的第7個字節(jié)成為0x02(正確填充),此時IV中的字節(jié)為0x24:
使用這種技巧,我們可以從后往前破解中間值里的每個字節(jié),最終得到解密后的值(盡管每次一個字節(jié))。下圖展示了完全破解后的IV值,此時整個數(shù)據(jù)塊都為填充值(0x08):
使用PadBuster進(jìn)行解密
(譯注:這段內(nèi)容為PadBuster的使用指南,在此略過,如果您對這部分內(nèi)容感興趣可以閱讀原文《Automated Padding Oracle Attacks with PadBuster》。)
加密任意的值
我們已經(jīng)知道如何利用Padding Oracle和PadBuster來依次破解每個加密的數(shù)據(jù)塊。現(xiàn)在,我們就來觀察下如何使用同樣的漏洞來加密任意數(shù)據(jù)。
可能您已經(jīng)發(fā)現(xiàn),一旦我們可以推斷出密文數(shù)據(jù)塊的中間值,我們便能通過操作IV的值來完全控制解密所得到的結(jié)果。例如,在前面的示例中,如果想要將密文中第一個數(shù)據(jù)塊解密為“TEST”這個值,您可以計算出它所需要的IV值,只要將目標(biāo)明文與中間值進(jìn)行異或操作即可。因此,只要您將字符串“TEST”(自然,還包括四個0x04字節(jié)作為填充)與中間值異或之后,便可以得到最終的IV,即0×6D,0×36,0×70,0×76,0×03,0×6E,0×22,0×39:
這種做法對于單個數(shù)據(jù)塊來說自然沒有問題,但如果我們想要用它來生成長度超過一個數(shù)據(jù)塊的值又該怎么辦呢?我們來看一個簡單通俗的實(shí)際案例。這次我們要生成一個加密的字符串“ENCRYPT TEST”而不僅僅是“TEST”。第一步,還是將文本分拆成數(shù)據(jù)塊,并補(bǔ)上必須的填充字節(jié),如下圖:
在構(gòu)造超過一個數(shù)據(jù)塊的值時,我們實(shí)際上是從最后一個數(shù)據(jù)塊開始,向前依次生成所需的密文。在這里,最后的數(shù)據(jù)塊與之前的相同,因此我們已經(jīng)知道以下的IV和密文能夠生成字符串“TEST”:
Request: http://sampleapp/home.jsp?UID=6D367076036E2239F851D6CC68FC9537
接下來,我們需要弄明白中間值6D367076036E2239在作為密文,而不是IV傳遞至應(yīng)用程序時會被如何解密。在這里只要使用與破解過程相同的技巧就行了,我們把它作為密文傳遞給應(yīng)用程序,并從全部為NULL的IV開始進(jìn)行暴力破解:
Request: http://sampleapp/home.jsp?UID=00000000000000006D367076036E2239
一旦我們通過暴力破解得到中間值之后,IV便可以用來生成我們想要的任意值。新的IV可以被放在前一個示例的前面,這樣便可以得到一個符合我們要求的,包含兩個數(shù)據(jù)塊的密文了。這個過程可以不斷重復(fù),這樣便能生成任意長度的數(shù)據(jù)了。
使用PadBuster加密任意的值
(譯注:這段內(nèi)容為PadBuster的使用指南,在此略過,如果您對這部分內(nèi)容感興趣可以閱讀原文《Automated Padding Oracle Attacks with PadBuster》。)