之前寫CBC翻轉(zhuǎn)攻擊的時(shí)候就在想什么時(shí)候能遇到Padding Oracle的題目hhhhh 想不到這么快就遇到了hhhhh
題目
題目ruby代碼如下:
#!/usr/bin/ruby -w
require 'openssl'
require 'base64'
def banner()
puts ' ____________________________________________'
puts '| |'
puts '| Welcome to our secure communication system |'
puts '| Our system is secured by AES |'
puts '| So...No key! No Message! |'
puts '|____________________________________________|'
puts ''
end
def option()
puts '1. Get the secret message.'
puts '2. Encrypt the message'
puts '3. Decrypt the message.'
puts 'Give your option:'
STDOUT.flush
op=gets
return op.to_i
end
def init()
file_key=File.new("./aeskey","r")
$key=file_key.gets
file_key.close()
end
def aes_encrypt(iv,data)
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
cipher.encrypt
cipher.key = $key
cipher.iv = iv
cipher.update(data) << cipher.final
end
def aes_decrypt(iv,data)
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
cipher.decrypt
cipher.key = $key
cipher.iv = iv
data = cipher.update(data) << cipher.final
end
def output_secret()
file_secret=File.new("./flag","r")
secret=file_secret.gets
file_secret.close
secret_enc=aes_encrypt("A"*16,secret)
secret_enc_b64=Base64.encode64(secret_enc)
puts secret_enc_b64
end
init
banner
while true do
begin
op=option
if op==1
output_secret
elsif op==2
puts "IV:"
STDOUT.flush
iv=Base64.decode64(gets)
puts "Data:"
STDOUT.flush
data=Base64.decode64(gets)
data_enc=aes_encrypt iv,data
puts Base64.encode64(data_enc)
puts "Encrytion Done"
STDOUT.flush
elsif op==3
puts "IV:"
STDOUT.flush
iv=Base64.decode64(gets)
puts "Data:"
STDOUT.flush
data=Base64.decode64(gets)
data_dec=aes_decrypt iv,data
puts data_dec
puts "Decrpytion Done"
STDOUT.flush
else
puts 'Wrong Option'
STDOUT.flush
end
rescue Exception => e
puts e.message
STDOUT.flush
retry
end
end
可以得出題目的基本信息:
- 1選項(xiàng):
輸出經(jīng)過aes-256-cbc加密的flag - 2選項(xiàng):
提供你的IV和要加密的數(shù)據(jù)逢慌,返回加密后的密文 - 3選項(xiàng):
提供你的IV和要解密的數(shù)據(jù)茎芋,不返回解密明文佣蓉,只返回解密成功是否
我們可以從源碼獲取到的信息有:
- 加密flag所采用的IV為16個(gè)字符
A
- 不能獲取到加密flag所用的密鑰
- 解密時(shí)IV與密文可控
背景知識(shí):
-
加密過程
- 首先將明文分成每X位一組愁溜,位數(shù)不足的是用特殊字符填充!!!Lㄐ弧!K昃朋沮!
X常見的為16位,也有32位
這里要注意缀壤,CBC的填充規(guī)則(有PKCS5和PKCS7樊拓,區(qū)別這里使用的是PKCS7 圖解如下)是缺少N位,就用 N 個(gè) '\xN'填充塘慕,如缺少10位則用 10 個(gè) '\x10'填充 - 然后生成初始向量IV(這里的初始向量如果未特定給出則隨機(jī)生成)和密鑰
- 將初始向量與第一組明文異或生成密文A
- 用密鑰加密密文A 得到密文A_1
- 重復(fù)3 將密文A_1與第二組明文異或生成密文B
- 重復(fù)4 用密鑰加密密文B_1
- 重復(fù)3-6 直到最后一組明文
- 將IV和加密后的密文拼接在一起筋夏,得到最終的密文(也可以不拼接)
-
解密過程
解密過程則是相反的
- 首先從最終的密文中提取出IV (IV為加密時(shí)指定的X位) //如果加密時(shí)沒有加入IV則不用提取
- 將密文分組
- 使用密鑰對(duì)第一組密文解密得到密文A,然后用IV進(jìn)行異或得到第一組明文
- 使用密鑰對(duì)第二組密文解密得到密文B图呢,然后用A與B進(jìn)行異或得到第二組明文
- 重復(fù)3-4 直到最后一組密文
攻擊
與CBC翻轉(zhuǎn)攻擊不同的地方是 我們這里不知道解密之后的明文条篷,只知道并可控IV和密文,對(duì)了 還有解密是否成功
解密是否成功這個(gè)點(diǎn)成為了padding oracle攻擊至關(guān)重要的一點(diǎn)蛤织,
因?yàn)槲覀冎纏adding只能為:
data 0x01 或
data 0x02 0x02 或
data 0x03 0x03 0x03 或
data 0x04 0x04 0x04 0x04 或
data 0x05 0x05 0x05 0x05 0x05 或
......
那如果出現(xiàn)以下這種padding的時(shí)候會(huì)怎么樣呢赴叹?
data 0x05 0x05
(正常來說這個(gè)padding應(yīng)為data 0x05 0x05 0x05 0x05 0x05
)
那解密之后的檢驗(yàn)就會(huì)出現(xiàn)錯(cuò)誤,因?yàn)閜adding的位數(shù)和padding內(nèi)容不一致
如果這個(gè)服務(wù)沒有catch這個(gè)錯(cuò)誤的話那么程序就會(huì)中途報(bào)錯(cuò)退出指蚜,表現(xiàn)為乞巧,如http服務(wù)的status code為500
那么這里就給了我們一個(gè)爆破的機(jī)會(huì),假如在第一組解密中摊鸡,我們輸入解密的IV為16個(gè)0x00的話绽媒,解密過程就為:
最后一位為0x3D,不符合padding規(guī)則
我們將IV的最后一位遞增免猾,然后提交是辕,在0x00到0xFF中,只會(huì)有一個(gè)異或middle最后一位之后會(huì)得到0x01猎提,也就是正確的padding获三,這時(shí)候服務(wù)正常解密(只是解密出來的結(jié)果不是原來的明文而已),則假設(shè)Plainttext為明文,middle為經(jīng)過aes解密之后尚未和IV異或的值石窑,IV[0]則為需要遍歷爆破的十六進(jìn)制,有
//根據(jù)
middle[最后一位] ^ IV[最后一位] = 0x01
middle[最后一位] = IV[最后一位] ^ 0x01
//因?yàn)檎5慕饷懿襟E是middle異或加密時(shí)使用的old_IV蚓炬,所以
Plainttext[最后一位] = middle[最后一位] ^ old_IV[最后一位]
到這里我們就能在不知道密鑰的情況下爆破出最后一位的明文了
接下來我們要爆破倒數(shù)第二位松逊,后兩位正確的padding應(yīng)該是
data 0x02 0x02
首先我們得先把最后一位調(diào)整成0x02,所以
IV[0] = middle[0] ^ 0x02
//那么解密的時(shí)候middle[0] ^ IV[0]就會(huì)始終等于0x02了
然后繼續(xù)從0x00爆破到0xFF肯夏,得到正確的解密提示之后將爆破得到的值異或old_IV[倒數(shù)第二位]就是Plainttext[倒數(shù)第二位了]
以此類推.....
但是在解密第二組及其以后的組的時(shí)候有一個(gè)注意的地方经宏,經(jīng)過aes解密之后的middle要異或的不再是IV了,而是前一組密文Q被鳌烁兰!
坑點(diǎn):
- 首先記得查看加密的初始IV是多少位,再根據(jù)這個(gè)位數(shù)將密文分組徊都!按組爆破沪斟!
- 其次是IV是每爆破出一位最好都要重新根據(jù)middle生成爆破位后面的位數(shù) (之前就是這個(gè)點(diǎn)坑了我一個(gè)通宵。暇矫。主之。。)
解題腳本 1:
from pwn import *
import base64 as b64
IV = ['\x00'] * 16
secret = 'nPQctp6AezY8BcGPjlYW8Pv+Fpo15LeatsVbj47jqgE='
secret1 = b64.b64decode(secret)[0:16]
secret2 = b64.b64decode(secret)[16:]
p = remote('10.188.2.20',10010)
middle = []
pt = ''
for x in xrange(0,16):
for y in xrange(0,256):
p.recvuntil("Give your option:\n")
p.sendline('3')
p.recvuntil("IV:\n")
p.sendline(b64.b64encode(''.join(IV))) #send your IV
p.recvuntil("Data:\n")
p.sendline(b64.b64encode(secret1)) #send your Data
# p.sendline(b64.b64encode(secret2)) #send your Data
res = p.recvuntil("\n")
# print res
if 'bad decrypt' in res:
IV[15-x] = chr(y)
elif 'Decrpytion Done' in res:
print IV
IV[15-x] = chr(ord(IV[15-x]) ^ (x + 1)) #to get the correct middle, just like ---> IV[0] ^ 0x01 = middle[0]
middle.append(ord(IV[15-x])) #store the correct middle
print middle
pt += chr(ord(IV[15-x]) ^ ord('A')) #first plaint text
# pt += chr(ord(IV[15-x]) ^ ord(secret1[15-x])) #second plaint text
for z in xrange(0,x + 1):
IV[15-z] = chr(middle[z] ^ (x + 2)) #generate the next new IV
break
else:
print res
exit()
if y == 255:
print '[!] Something wrong'
print x + 1
exit()
print '[!] Final IV : '
print IV
print '[!] Get middle : ', middle
print '[!] PlaintText is : ' + pt[::-1]
解題腳本 2:
from pwn import *
import base64 as b64
secret = 'nPQctp6AezY8BcGPjlYW8Pv+Fpo15LeatsVbj47jqgE='
secret1 = b64.b64decode(secret)[0:16]
secret2 = b64.b64decode(secret)[16:]
p = remote('10.188.2.20',10010)
middle = []
padding = ''
for x in xrange(1,17):
for y in xrange(0,256):
IV = "\x00" * (16-x) + chr(y) + padding
p.recvuntil("Give your option:\n")
p.sendline("3")
p.recvuntil("IV:\n")
p.sendline(b64.b64encode(IV))
p.recvuntil("Data:\n")
p.sendline(b64.b64encode(secret2))
res = p.recvuntil("\n")
if 'Decrpytion Done' in res:
middle.append(y ^ x) #calculate and store the correct middle
print middle
padding = ''
for z in middle:
padding = chr((x+1) ^ z) + padding #generate the next new IV tail
break
flag = ""
for x,y in zip(middle,secret1[::-1]):
# flag += chr(x ^ ord('A')) #for secret1
flag += chr(x ^ ord(y)) #for secret2
print flag[::-1]
作者水平有限 如有錯(cuò)誤請(qǐng)指出 Orz
參考文章 :
http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html
http://www.reibang.com/p/1851f778e579
http://www.reibang.com/p/9b4d3565de87