hash雜湊長度擴充套件攻擊解析(記錄一下,保證不忘)
起因
這是 ISCC 上的一道題目,抄 PCTF 的,並且給予了簡化。在利用簡化過的方式通過後,突然想起利用雜湊長度擴充套件攻擊來進行通關。雜湊長度擴充套件攻擊是一個很有意思的東西,利用了 md5、sha1 等加密演算法的缺陷,可以在不知道原始金鑰的情況下來進行計算出一個對應的 hash 值。 這裡是 ISCC 中題目中的 admin.php 的演算法:
$auth = false;if (isset($_COOKIE["auth"])) { $auth = unserialize($_COOKIE["auth"]); $hsh = $_COOKIE["hsh"]; if ($hsh !== md5($SECRET . strrev($_COOKIE["auth" ]))) { //$SECRET is a 8-bit salt $auth = false; }}else { $auth = false; $s = serialize($auth); setcookie("auth", $s); setcookie("hsh", md5($SECRET . strrev($s)));}
瞭解雜湊長度擴充套件攻擊
雜湊長度擴充套件攻擊適用於加密情況為:hash($SECRET, $message)
的情況,其中 hash 最常見的就是 md5、hash1。我們可以在不知道$SECRET
的情況下推算出另外一個匹配的值。如上例所給的 PHP 程式碼:
- 我們知道
md5($SECRET . strrev($_COOKIE["auth"]))
的值 - 我們知道
$hsh
的值 - 我們可以算出另外一個 md5 值和另外一個 $hsh 的值,使得
$hsh == md5($SECRET . strrev($_COOKIE["auth"]))
這樣即可通過驗證。如果要理解雜湊長度擴充套件攻擊,我們要先理解訊息摘要演算法的實現。以下拿 md5 演算法舉例。
md5 演算法實現
我們要實現對於字串abc
的 md5 的值計算。首先我們要把其轉化為 16 進位制。
補位
訊息必須進行補位,即使得其長度在對 512 取模後的值為 448。也就是說,len(message) % 512 == 448
1
,後面跟著無限個0
,直到 len(message) % 512 == 448
。在 16 進位制下,我們需要在訊息後補80
,就是 2 進位制的10000000
。我們把訊息abc
進行補位到 448 bit,也就是 56 byte。 補長度
補位過後,第 57 個位元組儲存的是補位之前的訊息長度。abc
是 3 個字母,也就是 3 個位元組,24 bit。換算成 16 進製為 0x18。其後跟著 7 個位元組的 0x00,把訊息補滿 64 位元組。
計算訊息摘要
計算訊息摘要必須用補位已經補長度完成之後的訊息來進行運算,拿出 512 bit的訊息(即64位元組)。 計算訊息摘要的時候,有一個初始的鏈變數,用來參與第一輪的運算。MD5 的初始鏈變數為:
A=0x67452301B=0xefcdab89C=0x98badcfeD=0x10325476
我們不需要關心計算細節,我們只需要知道經過一次訊息摘要後,上面的鏈變數將會被新的值覆蓋,而最後一輪產生的鏈變數經過高低位互換(如:aabbccdd -> ddccbbaa)後就是我們計算出來的 md5 值。
雜湊長度擴充套件攻擊的實現
問題就出在覆蓋上。我們在不知道具體 $SECRET 的情況下,得知了其 hash 值,以及我們有一個可控的訊息。而我們得到的 hash 值正是最後一輪摘要後的經過高地位互換的鏈變數。我們可以想像一下,在常規的摘要之後把我們的控制的資訊進行下一輪摘要,只需要知道上一輪訊息產生的鏈變數。 有點難理解,因為我都看的頭大。看起來我們把實現放在攻擊場景裡會更好。 仍然是如上的 PHP。因為其走了一點彎路(strrev、unserialize),所以我們修改一下。
$auth = "I_L0vE_L0li";if (isset($_COOKIE["auth"])) { $hsh = $_COOKIE["hsh"]; if ($hsh !== md5($SECRET . $_COOKIE["auth"])) { die("F4ck_U!"); }} else { setcookie("auth", $auth); setcookie("hsh", md5($SECRET . $auth)); die("F4ck_U!");}die("I_aM_A_L0li_dA_Yo~");
在實際環境中,我不知道 $SECRET 的值(我胡亂打的QAQ),只知道長度為 12。首先我們訪問一下看看。不出意外地被 f4ck 了。 Cookie 中的 auth 為I_L0vE_L0li
,hsh 為 7a84f420f8abe642237409f9d4daa851
。我們來進行雜湊長度擴充套件攻擊。
長度擴充套件
我們仍然要進行補位。因為 $SECRET 的長度是 12,我們用 12 個 x 來填補一下,緊跟著就是 auth 的值。然後我們把訊息補到 448 bit。接著進行補長度。然後後面跟著要附加的值,隨意什麼都可以。我這裡是I_aM_L01i
好了=v=。然後去掉前面的假的 $SECRET,得到最終的 $auth。
I_L0vE_L0li\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xB8\x00\x00\x00\x00\x00\x00\x00I_aM_L01i
urlencode之後為
I_L0vE_L0li%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%B8%00%00%00%00%00%00%00I_aM_L01i
計算雜湊
我在網上找了一個 C 語言的 md5 實現。因為 Python 的實現不能改初始的鏈變數。我修改了初始的鏈變數為經過高低位逆轉的 $hsh。 PS:原來的是7a84f420f8abe642237409f9d4daa851
A=0x20f4847aB=0x42e6abf8C=0xf9097423D=0x51a8dad4
然後我們對附加的值進行 md5 加密。附加的值為I_aM_L01i
。首先我們把前面 64 個位元組改為 64 個A
。這是為了使得除了 hash 本身以外其他的狀態完全一樣(原文:Then we take the MD5 of 64 'A's. We take the MD5 of a full (64-byte) block of 'A's to ensure that any internal values — other than the state of the hash itself — are set to what we expect)。實際上,前 64 個位元組填充什麼都無所謂。因為在進行我們的附加值的摘要之前,我們已經把鏈變數覆蓋了。 然後我們編譯並執行這個加密實現。 得到了一串密文,是1d00eac3f7da072d8365b0a7ae1fec42
。我們用 Firefox 的 firebug 外掛進行修改 Cookie。 重新整理後發現已經通過驗證。
總結
看起來很難理解,我本人也通宵了一晚上才搞定。當然因為我比較笨QAQ。總之,這是個很好玩的東西,大家可以去復現一下。 另外這個問題的解決方案為:hash($SECRET, hash($message))
。這樣就可以避免使用者可控 message 了。