前段時間學(xué)習(xí)了CBC模式的加密矛缨,針對其中的位反轉(zhuǎn)攻擊,許多比賽的題目中也都有所體現(xiàn)救氯,從一篇博客中找到了一個題目,想自己實現(xiàn)一下歌憨。
CBC字節(jié)翻轉(zhuǎn)攻擊
拿到題目后着憨,我將源碼修改了一下,主要是能夠在本地建立服務(wù)端务嫡,與同在一個子網(wǎng)的主機進行交互甲抖。
點擊下載源碼
源碼
提取碼:did3
將代碼在本地端用python編輯器運行,客戶端使用 (nc 服務(wù)端IP 20001)鏈接即可心铃。
如果在自己本地運行准谚,就是自己的IP。
簡單敘述一下CBC位反轉(zhuǎn)攻擊漏洞的來源:
這是CBC模式解密的圖解于个,從圖中我們可以看出氛魁,我們解密第一段密文(A)的時候暮顺,使用Key進行解密 厅篓,然后與初始化向量IV進行異或(XOR)運算,得到明文1(Plaintext_1)捶码。
第二段明文(Plaintext_2)則是將Ciphertext_2用Key解密后(得到 B )與上一段密文分組 A 進行異或運算得到 C 羽氮。以此類推。
我們可以看到惫恼,解密時后一段的明文是受前一段密文影響的档押,所謂的位反轉(zhuǎn)攻擊就是通過修改前一段的密文,來達到解密時祈纯,篡改了后一段明文的一種攻擊方式令宿。
舉個栗子:Eve想要篡改的是將 C 變成 M 。
我們知道 C = A ^ B
則 B = A ^ C
如果將 A 替換成(A ^ C )腕窥,則C = A ^ B = A ^ C ^ A ^ C = 0
那么再為C異或一次M粒没,就是我們想要的明文分組了。
我們來看看源碼當(dāng)中的漏洞出現(xiàn)在什么地方簇爆。
def mkprofile(email,client_socket):
if ((";" in email)):
return -1
prefix = "comment1=wowsuch%20CBC;userdata="
suffix = ";coment2=%20suchsafe%20very%20encryptwowww"
ptxt = prefix + email + suffix
#client_socket.send ("????"+encrypt_cbc(KEY, IV, ptxt))
return encrypt_cbc(KEY, IV, ptxt,client_socket)
def parse_profile(data,client_socket):
print data,'break 3'
ptxt = decrypt_cbc(KEY, IV, data.encode('hex'),client_socket) # ????
print data, 'break 4'
ptxt = ptxt.replace(" ", "") # ???????????
print data,'break 5'
#client_socket.send(bytes(ptxt))
if ";admin=true" in ptxt:
client_socket.send(bytes(FLAG))
#print FLAG
return 1
else:
client_socket.send(bytes("you are stupid"))
return 0
def dataReceived(data,client_socket):
if (data.startswith("getapikey:")):
data = data[10:]
resp = mkprofile(data,client_socket)
if (resp == -1):
client_socket.send(bytes("No Cheating!\n"))
else:
client_socket.send(bytes(resp))
# Decrypt Ciphertext and "parse" into Profile
elif (data.startswith("getflag:")):
client_socket.send(bytes("Parsing Profile...\n"))
data=data.strip()
data = data[8:].decode('hex')
if (parse_profile(data,client_socket) == 1):
client_socket.send(bytes(FLAG))
else:
client_socket.send(bytes("[BLACKBOX] You are a normal user.\n"))
else:
client_socket.send(bytes("\nyou should be admin"))
def connectionMade(self,client_socket):
self.key = os.urandom(16)
self.iv = os.urandom(16)
self.client_socket.send_docs()
def body(client_socket,i):
while True:
print('start ',i,'thread')
try:
client_socket.send(bytes("\nplease input string:\n"))
data = str(client_socket.recv(1024).decode('utf-8'))
client_socket.send(bytes("\ncipher:" + mkprofile(data,client_socket) + "\n"))
dataReceived(data,client_socket)
except:
break
server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind(('',20001))
server_socket.listen(128)
i=0
while True:
try:
client_socket, client_address = server_socket.accept()
thread.start_new_thread(body,(client_socket,i))
i=i+1
except:
print 'connect fail'
首先主體是body中的函數(shù)癞松,表述了建立連接之后會做的一系列事情:
def body(client_socket,i):
while True:
print('start ',i,'thread')
try:
client_socket.send(bytes("\nplease input string:\n"))
data = str(client_socket.recv(1024).decode('utf-8'))
client_socket.send(bytes("\ncipher:" + mkprofile(data,client_socket) + "\n"))
dataReceived(data,client_socket)
except:
break
在接受了客戶端的輸入之后,會通過dataReceived()函數(shù)入蛆,根據(jù)輸入的不同响蓉,走不同的路徑。
仔細(xì)研究dataReceived()和parse_profile()函數(shù)可以發(fā)現(xiàn)哨毁,我們獲取flag的條件是
if (parse_profile(data,client_socket) == 1):
client_socket.send(bytes(FLAG))
if ";admin=true" in ptxt:
client_socket.send(bytes(FLAG))
也就是說枫甲,用戶的輸入要帶有";admin=true",這樣,用戶的輸入被服務(wù)端接受后進行加密想幻,解密出來的明文才會帶有";admin=true"软能,但是嘗試著做題的朋友們肯定發(fā)現(xiàn),客戶端是不能直接輸入";admin=true"的举畸。
那么怎么破解呢查排,漏洞就在dataReceived()函數(shù)中:
與將用戶的輸入先加密再解密不同,當(dāng)用戶的輸入以"getflag:"為開頭的時候抄沮,在dataReceived()函數(shù)中調(diào)用的是parse_profile()函數(shù)跋核,直接進行解密操作,這樣我們就可以精心構(gòu)造一個密文叛买,讓它以"getflag:"作為開頭砂代,解密之后就帶有";admin=true"就可以得到flag了。
CBC加密模式每組16個字節(jié)率挣,我們要修改的密文在第三個分組刻伊,通過前面的表述,得知我們需要改第二個分組椒功。
我們先輸入"*admin=true"( * 也可以是其他的字符捶箱,只要將后面的是admin=true即可),得到一串密文动漾,將其作為data丁屎,也就是我們準(zhǔn)備開始構(gòu)造密文的雛形,只要將它對應(yīng) * 的一位在解密時解密出" ; "即可旱眯。
利用上面提到的方法構(gòu)造晨川,如下:
# coding=UTF-8
data = 'cb16a54c2fad7eb698eb620e66bd642daed5230138e49c75fd4e12ba0ffbaef38e8082ded7cfb240d086dae2ba1bd32f90d1f5085311101fa437a29c98d2672ba5e0125b8ad88af53ade51adc8ed299468c490b03df1ce5b8bf633201830693d'
data = data.strip()
data = data.decode("hex")
data = list(data)
print data
data[16] = chr(ord("*") ^ ord(";") ^ ord(data[16]))
print "異或",data
data = "".join(data)
print data
data = data.encode("hex")
print data
將得到的data前面加上"getflag:"作為輸入,即可得到flag删豺。
后記:
關(guān)于出題過程當(dāng)中出現(xiàn)的問題:
- 建立客戶端與服務(wù)器之間的交互共虑。
一開始參考了網(wǎng)上關(guān)于socket的使用方法,寫進代碼之后發(fā)現(xiàn)服務(wù)端只能與單個客
戶端通信呀页,不滿足實驗要求妈拌,然后在教員和同學(xué)的幫助下,使用thread赔桌,啟用多個線程供炎,每有一個客戶端連接進來,都會啟動一個新的線程疾党,這樣就解決了服務(wù)端與客戶端的交互問題音诫。 - 全局變量在各個用戶端的數(shù)據(jù)混亂
由于一開始的代碼中client_socket是全局變量,導(dǎo)致各個客戶端在client_socket.sent
發(fā)送的時候數(shù)據(jù)會混亂雪位。解決問題時竭钝,所有含有client_socket變量的函數(shù),都將它作為參數(shù)傳進函數(shù)。 -
程序在某個地方卡住香罐,后面的信息打印不出來卧波。
解決:在服務(wù)端程序卡住的代碼附近寫一些打印函數(shù),先定位到data = data[8:].decode(‘hex’)庇茫。
在這行代碼前后港粱,檢查data的類型以及長度,發(fā)現(xiàn)長度有問題旦签。沒有去掉字符串末尾的空格或者換行符查坪。
加上data=data.strip()。解決了此問題宁炫。