padding oracle attack相關之padding oracle attack

flurry_rain發表於2017-10-14

前段時間遇到一個挺有意思的題目,用到了padding oracle attack的相關知識,於是惡補了一下padding oracle attack相關內容,本著取之於民用之於民同時也可以方便自己以後複習的心態,我決定整理一下這幾天的所學所得,也算是留下點什麼hhh。

前面兩篇文章簡單介紹了padding oracle attack需要事先了解的一些知識,以便於理解,現在終於進入正題了hhh。下面我就我自己的理解說一下padding oracle attack的實現原理以及過程。

Padding Oracle Attack是針對CBC連結模式的攻擊,和具體的加密演算法無關,換句話說,這種攻擊方式不是對加密演算法的攻擊,而是利用了演算法的使用不當,進行的攻擊。選擇DES為例進行闡述,假設明文為: LittleHann(明文長度為10 8 < 10 < 16 即使用2個分組)經過DES加密(CBC模式)後,其密文為: EFC2807233F9D7C097116BB33E813C5E


如上圖所示,padding oracle攻擊其實就是通過伺服器的返回值判斷自己構建的IV值對不對,進而猜測出正確的中間值Intermediary Value,中間值再和獲取到的原本的IV值異或便可得到明文,因此攻擊成立的兩個重要假設前提:
(1)  攻擊者能夠獲得密文(Ciphertext),以及附帶在密文前面的IV(初始化向量)
(2)  攻擊者能夠觸發密文的解密過程,且能夠知道密文的解密結果

這裡有幾個概念要先理清一下:
(1)基於密碼學演算法的攻擊,往往第一個要搞清楚的是,我們在攻擊誰,或者準確的說我們的攻擊點在哪裡?在一個密碼學演算法中,有很多的引數(指攻擊者可以控制的引數),攻擊者往往是針對其中某一個或某一些引數進行破解,窮舉等攻擊。在Padding Oracle Attack攻擊中,攻擊者輸入的引數是IV+Cipher,我們要通過對IV的”窮舉”來請求伺服器端對我們指定的Cipher進行解密,並對返回的結果進行判斷。
(2)和SQL隱碼攻擊中的Blind Inject思想類似。Padding Oracle Attack也是利用了這個二值邏輯的推理原理,或者說這是一種”邊通道攻擊(Side channel attack)”。這種漏洞不能算是密碼學演算法本身的漏洞,但是當這種演算法在實際生產環境中使用不當就會造成問題。和盲注一樣,這種二值邏輯的推理關鍵是要找到一個”區分點”,即能被攻擊者用來區分這個的輸入是否達到了目的(在這裡就是尋找正確的IV)。 在web應用中,如果Padding不正確,則應用程式很可能會返回500的錯誤(程式執行錯誤);如果Padding正確,但解密出來的內容不正確,則可能會返回200的自定義錯誤,所以,這種區別就可以成為一個二值邏輯的”注入點”。
(4)明文分組和填充就是Padding Oracle Attack的根源所在,但是這些需要一個前提,那就是應用程式對異常的處理。當提交的加密後的資料中出現錯誤的填充資訊時,不夠健壯的應用程式解密時報錯,直接丟擲”填充錯誤”異常資訊(這個錯誤資訊在不同的應用中是不同的體現,在web一般是報500錯誤)。攻擊者就是利用這個異常來做一些事情,假設有這樣一個場景,一個WEB程式接受一個加密後的字串作為引數,這個引數包含使用者名稱、密碼。引數加密使。攻擊者只需要提交錯誤的密文,根據HTTP Code即可做出攻擊。

下面就我做的這個題目說明一下padding oracle attack的大概思路:

首先,我們要明確目的——找到正確的中間值,進而得到明文。我們先回憶一下CBC模式解密的過程,如下圖:

從圖中我們可以看出,第n-1塊密文是與第n塊密文中間值異或的IV值。根據PKCS #5標準,我們知道,填充N位需在明文後面加上n個0xN,同時我們還知道明文最後一定有填充值,也就是說倒數第二塊密文和最後一塊密文的中間值異或得到的值最後N位(0<N<密文分組長度)是0xN,而伺服器判斷正誤就是通過最後一組密文解密後的最後幾位是不是0xN,如果是就會返回正確不是則會返回錯誤(總之是兩種不同的返回值)。因此,我們可以先假設最後一組密文只填充了一位,也就是說最後明文的最後一位是0x1,我們只需隨意構造一個IV值,對IV值的最後一位從0x01到0x10(這是以AES為例,AES每個分組的長度為16位元組)逐個賦值並依次提交,當伺服器返回值不是錯誤時,此時取到的最後一位IV值與最後一組密文中間值的最後一位相異或得到的明文是0x01,把這個IV值與0x01相異或即可得到最後一位中間值。然後假設填充了兩位,先用得到的中間值和0x02異或得到IV值的最後一位,然後構造IV值的倒數第二位,取值範圍也是0x01到0x10,伺服器返回值不是錯誤的時候,倒數第二位IV值與最後一塊密文中間值的倒數第二位異或得到的值是0x02,把倒數第二位IV值與0x02異或便可得到倒數第二位中間值。以此類推,逐位試出所有的中間值,把得到的中間值與倒數第二塊密文相異或便可得到明文了。

這樣說看上去很亂,下面我就題目再捋一下padding oracle attack的過程:

(1)待破解的密文為9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31,採用了CBC模式下的AES演算法加密,下面是伺服器的登入程式(python):

s = None

def Oracle_Connect():
    import socket
    global s
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect(('128.8.130.16', 49101))
    except socket.error as e:
        print (e)
        return -1

    print ("Connected to server successfully.")

    return 0

def Oracle_Disconnect():
    if not s:
        print ("[WARNING]: You haven't connected to the server yet.")
        return -1

    s.close()
    print ("Connection closed successfully.")

    return 0

# Packet Structure: < num_blocks(1) || ciphertext(16*num_blocks) || null-terminator(1) >
def Oracle_Send(ctext, num_blocks):
    if not s:
        print ("[WARNING]: You haven't connected to the server yet.")
        return -1

    msg = ctext[:]
    msg.insert(0, num_blocks)
    msg.append(0)

    s.send(bytearray(msg))
    recvbit = s.recv(2)

    try:
        return int(recvbit)
    except ValueError as e:
        return int(recvbit[0])

(2)首先根據AES演算法的標準,我們把密文分組,每16位元組為一組,可以分為三組,並且新建用於IV值構造的IV列表和用於存放明文的P列表

data = "9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31"

ctext = [(int(data[i:i+2],16)) for i in range(0, len(data), 2)]
C = [ctext[:16], ctext[16:32], ctext[-16:]]
P = [[0] * 16, [0] * 16, [0] * 16]
IV = [[0] * 16, [0] * 16, [0] * 16]

(3)重複向伺服器提交密文,猜測正確的明文加密後的中間值(Intermediary Value),得到正確的中間值後,和已知的IV值互相異或便可以得到明文:

Oracle_Connect()
for bi in range(2):#迴圈兩次,解密兩個密文塊(有一塊密文塊是IV值)
    b = 2 - bi
    for k in range(16):#K+1代表padding的個數,例如K=0時填充1位,伺服器異或得到的值應該為0x01
        if b == 2:
            C1 = [C[0][:], C[1][:], C[2][:]]#解密C[2]對應的密文塊,C[1]是IV值
        if b == 1:
            C1 = [C[0][:], C[1][:]]#解密C[1]對應的密文塊,C[0]是IV值
        pos = 15 - k#pos對應的是IV值的第pos-1位
        for i in range(pos):            
            C1[b-1][i] = randrange(256)#構造IV值,從第0位到第pos—1位,隨機填入0到255(即0x00到0xFF)中的一個數字
                                       #剩下K+1位用於迴圈猜測
        for i in range(pos + 1, 16):
            C1[b-1][i] = (k + 1) ^ IV[b][i]#得到正確的中間值後存入IV,並將正確的中間值和K+1異或,
                                           #保證每次只迴圈猜測第pos-1位IV值
        ii = -1
        for i in range(256):#迴圈猜測IV值,正確的IV值滿足與正確中間值異或得到的數是0x(K+1)
            C1[b-1][pos] = i
            if b == 2:
                rc = Oracle_Send(C1[0][:] + C1[1][:] + C1[2][:], 3)
            if b == 1:
                rc = Oracle_Send(C1[0][:] + C1[1][:], 2)
            if rc == 1:
                ii = i#ii即正確的中間值與0x(K+1)異或得到的IV值
                break
        IV[b][pos] = ii ^ (k + 1)#IV值與K+1異或便可得到正確的中間值
        P[b][pos] = C[b-1][pos] ^ IV[b][pos]#中間值與原來的IV值異或便可得到對應位的明文
Oracle_Disconnect()

這樣我們便能得到正確的明文了:


完整的解題原始碼如下:

from oracle import *
import sys
from random import randrange

data = "9F0B13944841A832B2421B9EAF6D9836813EC9D944A5C8347A7CA69AA34D8DC0DF70E343C4000A2AE35874CE75E64C31"

ctext = [(int(data[i:i+2],16)) for i in range(0, len(data), 2)]
C = [ctext[:16], ctext[16:32], ctext[-16:]]
P = [[0] * 16, [0] * 16, [0] * 16]
IV = [[0] * 16, [0] * 16, [0] * 16]

Oracle_Connect()
for bi in range(2):#迴圈兩次,解密兩個密文塊(有一塊密文塊是IV值)
    b = 2 - bi
    for k in range(16):#K+1代表padding的個數,例如K=0時填充1位,伺服器異或得到的值應該為0x01
        if b == 2:
            C1 = [C[0][:], C[1][:], C[2][:]]#解密C[2]對應的密文塊,C[1]是IV值
        if b == 1:
            C1 = [C[0][:], C[1][:]]#解密C[1]對應的密文塊,C[0]是IV值
        pos = 15 - k#pos對應的是IV值的第pos-1位
        for i in range(pos):            
            C1[b-1][i] = randrange(256)#構造IV值,從第0位到第pos—1位,隨機填入0到255(即0x00到0xFF)中的一個數字
                                       #剩下K+1位用於迴圈猜測
        for i in range(pos + 1, 16):
            C1[b-1][i] = (k + 1) ^ IV[b][i]#得到正確的中間值後存入IV,並將正確的中間值和K+1異或,
                                           #保證每次只迴圈猜測第pos-1位IV值
        ii = -1
        for i in range(256):#迴圈猜測IV值,正確的IV值滿足與正確中間值異或得到的數是0x(K+1)
            C1[b-1][pos] = i
            if b == 2:
                rc = Oracle_Send(C1[0][:] + C1[1][:] + C1[2][:], 3)
            if b == 1:
                rc = Oracle_Send(C1[0][:] + C1[1][:], 2)
            if rc == 1:
                ii = i#ii即正確的中間值與0x(K+1)異或得到的IV值
                break
        IV[b][pos] = ii ^ (k + 1)#IV值與K+1異或便可得到正確的中間值
        P[b][pos] = C[b-1][pos] ^ IV[b][pos]#中間值與原來的IV值異或便可得到對應位的明文
Oracle_Disconnect()
print (P[1])
print (P[2])
print (map(lambda x: str(unichr(x)), P[1]))
print (map(lambda x: str(unichr(x)), P[2]))

a = ""
print (("the text is: %s") % a.join(text))




相關文章