CBC位反轉(zhuǎn)攻擊

前段時(shí)間學(xué)習(xí)了CBC模式的加密,針對其中的位反轉(zhuǎn)攻擊,許多比賽的題目中也都有所體現(xiàn),從一篇博客中找到了一個(gè)題目,想自己實(shí)現(xiàn)一下。
CBC字節(jié)翻轉(zhuǎn)攻擊
拿到題目后,我將源碼修改了一下,主要是能夠在本地建立服務(wù)端,與同在一個(gè)子網(wǎng)的主機(jī)進(jìn)行交互。
點(diǎn)擊下載源碼
源碼
提取碼:did3
將代碼在本地端用python編輯器運(yùn)行,客戶端使用 (nc 服務(wù)端IP 20001)鏈接即可。
如果在自己本地運(yùn)行,就是自己的IP。

簡單敘述一下CBC位反轉(zhuǎn)攻擊漏洞的來源:


解密

這是CBC模式解密的圖解,從圖中我們可以看出,我們解密第一段密文(A)的時(shí)候,使用Key進(jìn)行解密 ,然后與初始化向量IV進(jìn)行異或(XOR)運(yùn)算,得到明文1(Plaintext_1)。
第二段明文(Plaintext_2)則是將Ciphertext_2用Key解密后(得到 B )與上一段密文分組 A 進(jìn)行異或運(yùn)算得到 C 。以此類推。

我們可以看到,解密時(shí)后一段的明文是受前一段密文影響的,所謂的位反轉(zhuǎn)攻擊就是通過修改前一段的密文,來達(dá)到解密時(shí),篡改了后一段明文的一種攻擊方式。

舉個(gè)栗子: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ù),表述了建立連接之后會(huì)做的一系列事情:

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

在接受了客戶端的輸入之后,會(huì)通過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ù)端接受后進(jìn)行加密,解密出來的明文才會(huì)帶有";admin=true",但是嘗試著做題的朋友們肯定發(fā)現(xiàn),客戶端是不能直接輸入";admin=true"的。

那么怎么破解呢,漏洞就在dataReceived()函數(shù)中:
與將用戶的輸入先加密再解密不同,當(dāng)用戶的輸入以"getflag:"為開頭的時(shí)候,在dataReceived()函數(shù)中調(diào)用的是parse_profile()函數(shù),直接進(jìn)行解密操作,這樣我們就可以精心構(gòu)造一個(gè)密文,讓它以"getflag:"作為開頭,解密之后就帶有";admin=true"就可以得到flag了。

CBC加密模式每組16個(gè)字節(jié),我們要修改的密文在第三個(gè)分組,通過前面的表述,得知我們需要改第二個(gè)分組。


20170811165053720.png

我們先輸入"*admin=true"( * 也可以是其他的字符,只要將后面的是admin=true即可),得到一串密文,將其作為data,也就是我們準(zhǔn)備開始構(gòu)造密文的雛形,只要將它對應(yīng) * 的一位在解密時(shí)解密出" ; "即可。
利用上面提到的方法構(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。


image.png

后記:
關(guān)于出題過程當(dāng)中出現(xiàn)的問題:

  1. 建立客戶端與服務(wù)器之間的交互。
    一開始參考了網(wǎng)上關(guān)于socket的使用方法,寫進(jìn)代碼之后發(fā)現(xiàn)服務(wù)端只能與單個(gè)客
    戶端通信,不滿足實(shí)驗(yàn)要求,然后在教員和同學(xué)的幫助下,使用thread,啟用多個(gè)線程,每有一個(gè)客戶端連接進(jìn)來,都會(huì)啟動(dòng)一個(gè)新的線程,這樣就解決了服務(wù)端與客戶端的交互問題。
  2. 全局變量在各個(gè)用戶端的數(shù)據(jù)混亂
    由于一開始的代碼中client_socket是全局變量,導(dǎo)致各個(gè)客戶端在client_socket.sent
    發(fā)送的時(shí)候數(shù)據(jù)會(huì)混亂。解決問題時(shí),所有含有client_socket變量的函數(shù),都將它作為參數(shù)傳進(jìn)函數(shù)。
  3. 程序在某個(gè)地方卡住,后面的信息打印不出來。
    解決:在服務(wù)端程序卡住的代碼附近寫一些打印函數(shù),先定位到data = data[8:].decode(‘hex’)。


    image.png

在這行代碼前后,檢查data的類型以及長度,發(fā)現(xiàn)長度有問題。沒有去掉字符串末尾的空格或者換行符。
加上data=data.strip()。解決了此問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容