Week 4: CBC Padding Oracle Attack作业
文章目录
问题
week 4最后的编程作业是根据解密方解密时返回不同类型的错误的情况来还原明文, 原题如下:
In this project you will experiment with a padding oracle attack against a toy web site hosted at crypto-class.appspot.com . Padding oracle vulnerabilities affect a wide variety of products, including secure tokens .
This project will show how they can be exploited. We discussed CBC padding oracle attacks in week 4 (segment number 6), but if you want to read more about them, see a short description here or Vaudenay’s paper on this topic.
Now to business. Suppose an attacker wishes to steal secret information from our target web site crypto-class.appspot.com . The attacker suspects that the web site embeds encrypted customer data in URL parameters such as this:
http://crypto-class.appspot.com/po?er=f20bdba6ff29eed7b046d1df9fb7000058b1ffb4210a580f748b4ac714c001bd4a61044426fb515dad3f21f18aa577c0bdf302936266926ff37dbf7035d5eeb4
That is, when customer Alice interacts with the site, the site embeds a URL like this in web pages it sends to Alice. The attacker intercepts the URL listed above and guesses that the ciphertext following the “po?er=” is a hex encoded AES CBC encryption with a random IV of some secret data about Alice’s session.
After some experimentation the attacker discovers that the web site is vulnerable to a CBC padding oracle attack. In particular, when a decrypted CBC ciphertext ends in an invalid pad the web server returns a 403 error code (forbidden request). When the CBC padding is valid, but the message is malformed, the web server returns a 404 error code (URL not found).
Armed with this information your goal is to decrypt the ciphertext listed above. To do so you can send arbitrary HTTP requests to the web site of the form
http://crypto-class.appspot.com/po?er="your ciphertext here"
and observe the resulting error code. The padding oracle will let you
decrypt the given ciphertext one byte at a time. To decrypt a single byte you will need to send up to 256 HTTP requests to the site. Keep in mind that the first ciphertext block is the random IV. The decrypted message is ASCII encoded.
To get you started here is a short Python script that sends a ciphertext supplied on the command line to the site and prints the resulting error code. You can extend this script (or write one from scratch) to implement the padding oracle attack. Once you decrypt the given ciphertext, please enter the decrypted message in the box below.
This project shows that when using encryption you must prevent padding oracle attacks by either using encrypt-then-MAC as in EAX or GCM, or if you must use MAC-then-encrypt then ensure that the site treats padding errors the same way it treats MAC errors.
解题思路
解题前我们先了解一下CBC解密的一般流程:
- C2经过**解密后会形成中间结果I2
- I2与前一密文块C1(也可能是IV)异或后得到明文P2
Padding规则
CBC使用AES块加密时,分块长度为16bytes,最后一个明文块不够16bytes长度时需要进行填充。填充规则如下:
- 当最后一块明文长度恰好是16bytes时, 最后追加一个padding block, 即内容为0x10的16字节填充块
- 当最后一块明文长度不够16 bytes时,最后会填充字节, 填充字节内容为填充长度。
过程描述
猜解非填充块(非最后一块)
- 使用前一密文块作为当前**块的IV(即上图中C1)
- 从后往前按字节构造IV, 使得I2与C1异或后得到一个填充, 如C2最后一字节猜想为, 那么猜想IV最后一字节为填充字节, 则IV最后一字节需取值
- 将2生成的新的密文发送到解密方, 若返回结果为padding正常, 则对应位置的明文则为, 进入第4步;若返回padding失败, 则进入第2步尝试另一个
- 将IV最后一个值填充为, 猜想倒数第二个字节为, 然后重新进入第2步,尝试猜解倒数第二个字节,直到所有字节猜解完成。
猜解填充块(最后一块)
- 使用前一密文块作为当前**块的IV(即上图中C1)
- 从后往前按字节构造IV, 使得I2与C1异或后得到一个填充, 如C2最后一字节猜想为, 那么猜想IV最后一字节为填充字节, 则IV最后一字节需取值
- 将2生成的新的密文发送到解密方, 若返回结果为padding正常, 则修改IV倒数第二个字符值以打破填充规则,若猜解依然返回padding正常, 则为真实值;若猜解错误, 则继续猜解其他值。
- 若3猜解正确, 其余字节同猜解非填充块操作
样例程序
代码
import requests
from requests import ConnectionError
from typing import List, Tuple
from math import floor
import pickle
from time import sleep
def padding_query(c: str) -> bool:
"""
查询padding
:param c: 密文
:return: 是否成功
"""
url = "http://crypto-class.appspot.com/po"
rsp = requests.get(url, params={
"er": c
}
)
status_code = rsp.status_code
rsp.close()
return True if status_code == 404 else False
def convert_hex_str_to_int_list(a: str) -> List[int]:
return [int(a[i: i+2], 16) for i in range(0, len(a), 2)]
def split_iv_ciphers(cipher: str) -> Tuple[List[int], List[List[int]]]:
"""
拆分出iv和密文块
:param cipher:
:return:
"""
hex_cipher = convert_hex_str_to_int_list(cipher)
block_size = 16
block_num = floor(len(hex_cipher) / block_size)
iv = hex_cipher[0:block_size]
cipher_list = list()
for i in range(1, block_num):
cipher_list.append(hex_cipher[i*block_size: (i+1)*block_size])
return iv, cipher_list
def convert_int_list_to_hex_str(iv: List[int], cipher: List[int]) -> str:
result_list = []
for i in iv:
result_list.append("%02x" % i)
for j in cipher:
result_list.append("%02x" % j)
return "".join(result_list)
def contruct_attack_iv(iv: List[int], value: int, padding_num: int) -> List[int]:
"""
构建iv
:param iv:
:param value:
:param padding_num: padding数
:return:
"""
iv_compute = iv[:]
iv_compute.reverse()
for (idx, val) in enumerate(iv_compute):
if idx == (padding_num - 1):
iv_compute[idx] = (val ^ value ^ padding_num)
elif idx < padding_num - 1:
iv_compute[idx] = (val ^ padding_num)
else:
pass
iv_compute.reverse()
return iv_compute
def padding_attack(iv: List[int], cipher: List[int], last_block: bool = False) -> List[int]:
"""
padding攻击
:param iv:
:param cipher:
:return:
"""
total_bytes = len(cipher)
content_list = [ -1 for i in range(total_bytes)]
block_size = len(iv)
iv_construct = iv[:]
for j in range(block_size - 1, -1, -1):
k = 0
while k < 256:
iv_trailed = iv_construct[:]
iv_trailed = contruct_attack_iv(iv_trailed, k, block_size - j)
hex_str = convert_int_list_to_hex_str(iv_trailed, cipher)
try:
if padding_query(hex_str): # 找到一个
if last_block and j == 15:
# 有填充的情况
iv_trailed = iv_construct[:]
iv_trailed[j - 1] = 0
iv_trailed = contruct_attack_iv(iv_trailed, k, block_size - j)
hex_str = convert_int_list_to_hex_str(iv_trailed, cipher)
if padding_query(hex_str):
print("j: {}, k: {}, result: True, content: {}".format(j, k, content_list))
content_list[j] = k
iv_construct[j] = iv[j] ^ k
break
else:
print("j: {}, k: {}, result: True, content: {}".format(j, k, content_list))
content_list[j] = k
iv_construct[j] = iv[j] ^ k
break
else:
sleep(0.1)
except ConnectionError as e:
sleep(1)
else:
k += 1
else:
raise Exception("no valid padding found")
return content_list
if __name__ == '__main__':
cipher = 'f20bdba6ff29eed7b046d1df9fb7000058b1ffb4210a580f748b4ac714c001bd4a61044426fb515dad3f21f18aa577c0bdf302936266926ff37dbf7035d5eeb4'
iv, cipher_list = split_iv_ciphers(cipher)
result_list = list()
for i in range(len(cipher_list)):
if i == len(cipher_list) - 1:
result_list.extend(padding_attack(iv, cipher_list[i], True))
else:
result_list.extend(padding_attack(iv, cipher_list[i]))
iv = cipher_list[i]
with open("result-test.txt", "wb") as f:
pickle.dump(result_list, f)
print("result: {}".format("".join(map(lambda x: chr(x), result_list))))
print("done!")
结果
引用
- https://www.cnblogs.com/ntestoc/p/11044433.html
- https://blog.csdn.net/csh1989/article/details/38457377?utm_source=blogxgwz7
上一篇: Dockerfile指令