1. 程式人生 > >hash雜湊長度擴充套件攻擊解析(記錄一下,保證不忘)

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

。當訊息長度不滿 448 bit 時(注意是位,而不是字串長度),訊息長度達到 448 bit 即可。當然,如果訊息長度已經達到 448 bit,也要進行補位。補位是必須的。 補位的方式的二進位制表示是在訊息的後面加上一個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 了。