摘自:
1、http://www.freebuf.com/articles/web/69264.html
2、https://www.cnblogs.com/pcat/p/5478509.html
0×01 引言
為什么會想到這個呢?上周末做了“強網杯”的童鞋們應該都能知道吧,它其中有個密碼學的題目就是考的這一點。
0×02 sha1的hash原理
說到要解釋sha1的原理其實是非常復雜的,反正我這種智商的暫時還無法理解。所以,只能從面上跟大家談一下我的理解。
首先,當hash函數拿到需要被hash的字符串后,先將其字節長度整除64,取得余數。如果該余數正好等於56,那么就在該字符串最后添加上8個字節的長度描述符(具體用bit表示)。如果不等於56,就先對字符串進行長度填充,填充時第一個字節為hex(80),其他字節均用hex(00)填充,填充至余數為56后,同樣增加8個字節的長度描述符(該長度描述符為需要被hash的字符串的長度,不是填充之后整個字符串的長度)。以上過程,稱之為補位。
補位完成后,字符串以64位一組進行分組(因為上面的余數為56,加上8個字節的長度描述符后,正好是64位,湊成一組)。字符串能被分成幾組就會進行多少次“復雜的數學變化”。每次進行“復雜的數學變化”都會生成一組新的registers值供下一次“復雜的數學變化”來調用。第一次“復雜的數學變化”會調用程序中的默認值。當后面已經沒有分組可以進行數學變化時,該組生成的registers值就是最后的hash值。
在sha1的運算過程中,為確保同一個字符串的sha1值唯一,所以需要保證第一次registers的值也唯一。所以在sha1算法中,registers具有初始值。如上圖中的registers值0。
Hash值的隨機性完全依賴於進行“復雜的數學變化”時輸入的registers值和該次運算中字符串分組的數據。如果進行“復雜數學變化”時輸入的registers值和該次運算的字符串分組相同,那么他們各自生成的新的registers值也相同。
0×03 舉個栗子
當需要被hash的字符串為str_a = ”123456”,程序首先判斷,len(str_a) % 64 == 56是否成立。這里很明顯不成立。那么程序就進行補位操作。首先補位成余數為56的長度。
如上圖,藍色字體就為程序對該字符串進行補位的數據。當滿足len(str_a) % 64 == 56后,程序就在該字符串的后面添加8個字節的長度描述符。注意,此處的長度為原始需要被hash的長度。也就是len(str_a) = 6字節*8bit/字節= 48bit=0x30bit。
補位+長度描述符=64個字節,正好是一個分組。所以此處只要進行一次復雜的數學變化就可以了。程序根據該64個字節的數據和registers值0生成新的registers值1。那么該新的registers值1就是str_a的sha1值
0×04 如何利用?
講了這么多,好像都沒講到如何利用該擴展攻擊。那么下面,重點來了。
我們還是利用這篇文章上面的例子進行講解,轉到FreeBuf之前文章。
簡單來說,就是服務器上會生成一個salt值,該salt值你是不可預測的。但是你又知道了sha1(salt+filename)的值,該filename的值你也是知道的。假設此處的filename的值report.pdf,最后sha1的值為:0a8d538b724c6f2b4288526eb540ee7c。為了方便理解,我們繼續假設salt的長度為16位。
將上圖的字符串進行sha1操作時,同樣先進行整除,然后取余。最后再補上8位的長度描述符。補位+添加長度描述符后的字符串如下圖:
該長度也就滿足了64位的分組,只需要進行一次“復雜的數學運算”就可以得到最后的sha1值了。
下面請各位看官思考如何進行下面一個字符串的sha1操作。
同樣,還是先進行分組。由於該字符串的長度大於64個字節,且小於128個字節,所以要分成兩組,需要進行兩次“復雜的數學運算”。這個時候我們發現,第一個分組的數據和上圖中補碼后的數據完全一樣,又因為他們都是第一個分組,初始的registers值也一樣。那么經過第一輪“復雜的數學運算”,他們各自生成的registers值也同樣是相同的。唯一不同的是,由於上面的長度小於64字節,所以只需要進行一輪運算便得到了最后的sha1值。然后這里的字符串有兩個分組,需要將第一輪更新的registers值(也就是第一輪運算出來的sha1值)作為第二輪“復雜的數學運算”的registers值,然后才能得出最終的sha1值。
根據上面例子就說明,如果salt的值你不知道,但是你知道長度,又知道sha1(salt),那么就也就可以知道sha1(salt+“填充數據”+“任意可控數據”).這里的salt+“填充數據”就是對salt進行sha1時所補全的數據+最后8位的長度描述符。一般來說,salt+”填充數據”的長度就是64字節,正好是一個分組。如果salt的長度就大於了56個字節,那么加入填充數據后的長度應該是N個64字節,等於N個分組。
為什么?你可以想象,sha1程序再對(salt+“填充數據”+“任意可控數據”)進行hash時,只需要進行第二輪及第二輪以后的運算。因為第一輪運算后的registers值就是sha1(salt)的值,該值你已經知道了。
Hashdump安裝和使用
HashPump是一個借助於OpenSSL實現了針對多種散列函數的攻擊的工具,支持針對MD5、CRC32、SHA1、SHA256和SHA512等長度擴展攻擊。而MD2、SHA224和SHA384算法不受此攻擊的影響,因其部分避免了對狀態變量的輸出,並不輸出全部的狀態變量。
(至於別的文章提到了MD4、RIPEMD-160、SHA-0、WHIRLPOOL等也可以構造長度擴展攻擊,等以后再研究。)
git clone https://github.com/bwall/HashPump apt-get install g++ libssl-dev cd HashPump make make install
至於想在python里實現hashpump,可以使用hashpumpy這個插件:
pip install hashpumpy
推薦在linux里使用,使用方法可以這樣獲取:
python
>>> import hashpumpy >>> help(hashpumpy.hashpump)
2、HashPump用法
這里以一個實驗吧題目為例,關鍵的代碼大概如下:
<?php
$secret="XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security! $username="admin"; $password = $_POST["password"]; if($COOKIE["getmein"] === md5($secret . urldecode($username . $password))){ echo "Congratulations! You are a registered user.\n"; die ("The flag is ". $flag); }else{ die("Your cookies don't match up! STOP HACKING THIS SITE."); } ?>
在題目里可以得到:
md5($secret."adminadmin")的值為571580b26c65f306376d4f64e53cb5c7
稍微整理下我們已經知道的:
$secret是密文,長度為15,如果再算上后面第一個admin,長度就是20
而數據是admin
簽名(哈希值)是571580b26c65f306376d4f64e53cb5c7
這時候我們使用HashPump,附加數據至少1位以上:
# hashpump
Input Signature: 571580b26c65f306376d4f64e53cb5c7
Input Data: admin
Input Key Length: 20 Input Data to Add: pcat
或者直接
hashpump -s 571580b26c65f306376d4f64e53cb5c7 -d admin -k 20 -a pcat
就會得到
3e67e8f0c05e1ad68020df30bbc505f5
admin\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\xc8\x00\x00\x00\x00\x00\x00\x00pcat
第一個是新的簽名,把它設置到cookies的getmein里。
第二個先把\x替換為%后,post提交
password=admin%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%c8%00%00%00%00%00%00%00pcat
就可以通過了。
-------
ps.提供一個基於HashPump的在線網站:







