淺談HASH長度擴充攻擊

Yunen的部落格發表於2020-09-07

前言

最近在做CTF題的時候遇到這個考點,想起來自己之前在做實驗吧的入門CTF題的時候遇到過這個點,當時覺得難如看天書一般,現在回頭望去,仔細琢磨一番感覺也不是那麼難,這裡就寫篇文章記錄一下自己的學習的過程。

正文

何為HASH長度擴充攻擊?

簡單的說,由於HASH的生成機制原因,使得我們可以人為的在原先明文資料的基礎上新增新的擴充字元,使得原本的加密鏈變長,進而控制加密鏈的最後一節,使得我們得以控制最終結果。

這裡我們以MD5加密演算法為例子。

MD5長度擴充攻擊

下面是個簡單的PHP例子。

<?php
include "flag.php";
$secretKey = 'xxxxxx'; #xxx為未知內容,但長度已知為6。
$v1 = $_GET['str'];
$sign = $_GET['sign'];
$token = md5($secretKey.$v1);
if($v1 === 'test') {
    die($token); #token=2df51a84abc64a28740d6d2ae8cd7b16
} else {
    if($token === $sign) {
        die($flag);
    }
}
?>

在這個例子中,我們需要使得變數$token與我們輸入的sign引數滿足一致才會輸出flag。

而由於我們無法知道變數$secretKey的內容,所以無法得到$token的值,故而看似是沒有辦法獲取到flag的死局,而這時便輪到我們的擴充攻擊來大顯身手了。

MD5演算法流程

若想搞清楚原理,其演算法的流程是必須瞭解的。不過我們無需去關心那些複雜的運算,只需要知道的大概的一個流程就OK了。

這裡借一張神圖:

MD5加密流程

摘自:雜湊長度擴充攻擊(Hash Length Extension Attacks)

看不懂也沒關係,相信你看完我這篇文章後再返回來看這張圖就很清晰明瞭了。

我們還是舉個例子,對於字串aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbb(64個a、3個b)。長度為19個字元,且根據ASCII表,字元a、b的十六進位制分別為0x61、0x62。

而我們知道,1位十六進位制相當於4位二進位制表示(16=2^4)。所以對於64個字元a的長度來說,其二進位制長度為:字元長度*二進位制位數2*十六進位制轉二進位制位數擴充4=64*2*4=512

對於MD5演算法來說,我們需要將原資料進行分塊處理,以512位個二進位制資料為一塊。”最後“一塊的處理分為以下幾種情況:

  • 明文資料的二進位制資料長度<=448,填充padding(無意義佔位)資料使其長度為448,再新增原始明文資料的二進位制長度資訊(64位)使其長度為512位即可。
  • 448<明文資料的二進位制資料長度<=512,填充padding資料至下一塊的448位,而後再新增原始明文資料的二進位制長度資訊(64位)使其長度為512位即可。

兩種情況如下圖:

第一種情況

第二種情況

注意:每塊資料的長度均為512位二進位制,圖中的資料我沒有全都用二進位制來表示,將明文資料分塊之後就可以與向量進行運算了。

對於padding資料(長度不定)來說:首位二進位制位1,其餘位為0.

對於長度資訊位(長度8Byte=64bit)來說,從低位向高位數,如上圖的長度資訊:f0 03 00 00 00 00 00 00即代表0x03f0,其對應的十進位制為1008,即為64+62=126個字元的二進位制位數(一個字元1Byte即8bit)。

對於MD5演算法來說,有一串初始向量如下:

A=0x67452301
B=0xefcdab89
C=0x98badcfe
D=0x10325476

這串初始向量的值是固定的,作為與第一塊資料運算的原始向量。

當這串向量與第一塊資料塊運算之後,得到了一串新的向量值,這串新的向量值接著與第二塊資料塊參加運算,直到最後一塊資料塊。

如下圖所示:

向量運算

而最後的MD5值就是這最後的向量串經過如下轉換的結果。

如向量串:

A=0xab45bc01
B=0x6a64bb53
C=0x23ba8afe
D=0x46847a62

先兩兩為一組進行組合,得到如下資料:

ab 45 bc 01
6a 64 bb 53
23 ba 8a fe
46 84 7a 62

再進行高低位互換,得到如下資料:

01 bc 45 ab
53 bb 64 6a
fe 8a ba 23
62 7a 84 46

最終拼接得到MD5值:01bc45ab53bb646afe8aba23627a8446

現在,讓我們回到開始的那個例子。

對於MD5值:2df51a84abc64a28740d6d2ae8cd7b16。我們可以根據MD5與向量互轉規則,將MD5轉成md5($secretKey + "test")的最終向量值(A'、B'、C'、D'):

A'=0x841af52d
B'=0x284ac6ab
C'=0x2a6d0d74
D'=0x167bcde8

過程如圖:

例子向量運算過程

這時候我們修改$v1變數的內容為:

"test" + [0x80 + (0x0)*45] + [0x50 + 0x0*7] + "abc"
相當於:
"test" + padding資料 + 長度資料 + "abc"

則上述過程則被延續成下圖所示:

延續運算過程

而對於上述運算過程來說,我們知道了倒數第二個向量串的內容和最後一個資料塊,這樣一來,最終的MD5值我們也可以自己通過MD5演算法計算出來了。

擴充

如同MD5演算法那般分組後與向量運算的流程被統稱為Merkle–Damgård結構。

而同樣使用此結構的HASH演算法還有:SHA1、SHA2等

hashpump

hashpump是一個專門生成MD5長度擴充攻擊payload的工具。

Github倉庫:https://github.com/bwall/HashPump

安裝方法:

#Linux
git clone https://github.com/bwall/HashPump.git
apt-get install g++ libssl-dev
cd HashPump
make
make install

安裝好之後在終端裡輸入hashpump,回車即可:

hashpump

以之前的例子為例,使用hashpump生成payload:

生成payload

故我們的EXP即為(\x%代替):

/?str=test%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%00%00%00%00%00%00%00%00%00%00%00%00%00P%00%00%00%00%00%00%00abc&sign=bac6cb2d585d2de3f5f48f2759d2e5a7

成功讀取FLAG:

成功讀取FLAG

CTF

相關CTF題可供練習:

  • [De1CTF2019]SSRFMe
  • 實驗吧-讓我進去

後記

其實這個知識點確實不難,但是回看兩年前的自己,那時候是真的完完全全看不懂看不明白,但是現在只花了十幾分鍾就可以說是掌握這個知識點了。原來我們不知不覺間也對知識的認知又提升了一個臺階,原先難如天書的內容現在看來也不過爾爾,原先看不到、接觸不到的知識,現在也有信心能夠去嘗試去學習、去理解並掌握。學習本該如此,如攀登高山一般,只有開始攀登,才有機會看得到山腳下看不到的風景,也唯有不斷攀登,才能看到更多更多風景。

參考

相關文章