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解密的一般流程:

CBC模式某分组的解密过程

  1. C2经过密钥解密后会形成中间结果I2
  2. I2与前一密文块C1(也可能是IV)异或后得到明文P2

Padding规则

CBC使用AES块加密时,分块长度为16bytes,最后一个明文块不够16bytes长度时需要进行填充。填充规则如下:

  • 当最后一块明文长度恰好是16bytes时, 最后追加一个padding block, 即内容为0x10的16字节填充块
  • 当最后一块明文长度不够16 bytes时,最后会填充 16 − 最 后 一 块 长 度 16 - 最后一块长度 16字节, 填充字节内容为填充长度。

过程描述

猜解非填充块(非最后一块)

  1. 使用前一密文块作为当前破解块的IV(即上图中C1)
  2. 从后往前按字节构造IV, 使得I2与C1异或后得到一个填充, 如C2最后一字节猜想为 k k k, 那么猜想IV最后一字节为填充字节, 则IV最后一字节需取值 I V [ l a s t _ b y t e ] ⨁ k ⨁ 0 x 01 IV[last\_byte] \bigoplus k \bigoplus 0x01 IV[last_byte]k0x01
  3. 将2生成的新的密文发送到解密方, 若返回结果为padding正常, 则对应位置的明文则为 k k k, 进入第4步;若返回padding失败, 则进入第2步尝试另一个 k k k
  4. 将IV最后一个值填充为 V [ l a s t _ b y t e ] ⨁ k ⨁ 0 x 02 V[last\_byte] \bigoplus k \bigoplus 0x02 V[last_byte]k0x02, 猜想倒数第二个字节为 j j j, 然后重新进入第2步,尝试猜解倒数第二个字节,直到所有字节猜解完成。

猜解填充块(最后一块)

  1. 使用前一密文块作为当前破解块的IV(即上图中C1)
  2. 从后往前按字节构造IV, 使得I2与C1异或后得到一个填充, 如C2最后一字节猜想为 k k k, 那么猜想IV最后一字节为填充字节, 则IV最后一字节需取值 I V [ l a s t _ b y t e ] ⨁ k ⨁ 0 x 01 IV[last\_byte] \bigoplus k \bigoplus 0x01 IV[last_byte]k0x01
  3. 将2生成的新的密文发送到解密方, 若返回结果为padding正常, 则修改IV倒数第二个字符值以打破填充规则,若猜解 k k k依然返回padding正常, 则 k k k为真实值;若 k k k猜解错误, 则继续猜解其他值。
  4. 若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
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值