🏳️
The CTF Recipes
  • Introduction
  • Cryptography
    • Introduction
    • General knowledge
      • Encoding
        • Character encoding
          • ASCII
          • Unicode
          • UTF-8
        • Data encoding
          • Base16
          • Base32
          • Base64
      • Maths
        • Modular arithmetic
          • Greatest Common Divisor
          • Fermat's little theorem
          • Quadratic residues
          • Tonelli-Shanks
          • Chinese Remainder Theorem
          • Modular binomial
      • Padding
        • PKCS#7
    • Misc
      • XOR
    • Mono-alphabetic substitution
      • Index of coincidence
      • frequency analysis
      • Well known algorithms
        • 🔴Scytale
        • 🔴ROT
        • 🔴Polybe
        • 🔴Vigenere
        • 🔴Pigpen cipher
        • 🔴Affine cipher
    • Symmetric Cryptography
      • AES
        • Block Encryption procedure
          • Byte Substitution
          • Shift Row
          • Mix Column
          • Add Key
          • Key Expansion / Key Schedule
        • Mode of Operation
          • ECB
            • Block shuffling
              • Challenge example
            • ECB Oracle
              • Challenge example
          • CBC
            • Bit flipping
              • Challenge example
            • Padding oracle
              • Challenge example
          • OFB
            • Key stream reconstruction
            • Encrypt to Uncrypt
  • 🛠️Pwn
    • General knowledge
      • STACK
        • Variables storage
        • Stack frame
      • PLT and GOT
      • HEAP
        • HEAP operations
        • Chunk
        • Bins
        • Chunk allocation and reallocation
      • Syscall
    • Architectures
      • aarch32
        • Registers
        • Instruction set
        • Calling convention
      • aarch64
        • Registers
        • Instruction set
        • Calling convention
      • mips32
        • Registers
        • Instruction set
        • Calling convention
      • mips64
        • Registers
        • Instruction set
        • Calling convention
      • x86 / x64
        • Registers
        • Instruction set
        • Calling convention
    • Stack exploitation
      • Stack Buffer Overflow
        • Dangerous functions
          • gets
          • memcpy
          • sprintf
          • strcat
          • strcpy
        • Basics
          • Challenge example
        • Instruction pointer Overwrite
          • Challenge example
        • De Bruijn Sequences
        • Stack reading
          • Challenge example
      • Format string
        • Dangerous functions
          • printf
          • fprintf
        • Placeholder
        • Data Leak
          • Challenge example
        • Data modification
          • Challenge example
      • Arbitrary code execution
        • Shellcode
        • ret2reg
        • Code reuse attack
          • Ret2plt
          • Ret2dlresolve
          • GOT Overwrite
          • Ret2LibC
          • Leaking LibC
          • Ret2csu
          • Return Oriented Programming - ROP
          • Sigreturn Oriented Programming - SROP
          • Blind Return Oriented Programming - BROP
            • Challenge example
          • 🔴Call Oriented Programming - COP
          • 🔴Jump Oriented Programming - JOP
          • One gadget
        • Stack pivoting
    • 🛠️Heap exploitation
      • Heap overflow
        • Challenge example
      • Use after free
        • Challenge example
      • 🛠️Double free
      • 🔴Unlink exploit
    • Protections
      • Stack Canaries
      • No eXecute
      • PIE
      • ASLR
      • RELRO
    • Integer overflow
Powered by GitBook
On this page
  • Source code
  • Exploitation
  • Vulnerability detection
  • POC
  • Full exploitation
  • Using Libraries
  • Exercice
  1. Cryptography
  2. Symmetric Cryptography
  3. AES
  4. Mode of Operation
  5. CBC
  6. Padding oracle

Challenge example

Source code

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import json
import os

FLAG = ?
KEY = ?

def encryptFlag():
    data = {"flag": FLAG}

    plaintext = json.dumps(data).encode()
    padded = pad(plaintext, 16)
    iv = os.urandom(16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    try:
        encrypted = iv + cipher.encrypt(padded)
        print(f"Here is your token : {encrypted.hex()}")
    except ValueError as e:
        print({"error": str(e)})
        return

def checkToken():
    token = input("Token : ")
    token = bytes.fromhex(token)
    iv = token[:16]
    token = token[16:]

    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    try:
        plaintext = cipher.decrypt(token)
        unpadded = unpad(plaintext, 16)
        data = json.loads(unpadded)
    except ValueError as e:
        print({"error": str(e)})
        return

    if data["flag"] == FLAG:
        print("Token is valid")


print("Welcome !")
encryptFlag()
checkToken()

The objective is to break the ciphertext to retrieve the flag value.

Exploitation

Vulnerability detection

In order to exploit the padding oracle attack, there are 2 things that need to be checked:

  • We can send as many time as we want a ciphertext to decrypt.

$ nc 127.0.0.1 1337
Welcome !
Here is your token : 218b43506e242a8be58b281d147a75f2709c2695687e6c42791677a4cc9e3a391873aeb156a1f1f3bbc0b9d1ab21d509687606b976351f4998d4210c0e875930
Token : 218b43506e242a8be58b281d147a75f2709c2695687e6c42791677a4cc9e3a391873aeb156a1f1f3bbc0b9d1ab21d509687606b976351f4998d4210c0e875930
Token is valid

$ nc 127.0.0.1 1337
Welcome !
Here is your token : c5bf8a9b3e59cd2d80e06a99bc36cbdefea51ee96ae74ac53ecc2e54da7a229e60b7cceacc4d8b96b490f68aa3a800975679289a314ea455c58516947899f216
Token : 218b43506e242a8be58b281d147a75f2709c2695687e6c42791677a4cc9e3a391873aeb156a1f1f3bbc0b9d1ab21d509687606b976351f4998d4210c0e875930
Token is valid
  • The application returns an error in case of padding error.

$ nc 127.0.0.1 1337
Welcome !
Here is your token : e7f357f5b63a730c095a9f7d3ff834e3786deed41d4b16983591ace7c4698ce205cfe4ab168ea4c6abdc58f3cae7bf425ab76dc95c03747f66dfd86edc955ae1
Token : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
{'error': 'Padding is incorrect.'}

POC

Then the real plaintext byte will be bruteforce_result ⊕ original_block_byte ⊕ 0x01

from pwn import *
from rich.console import Console

console = Console()
context.log_level = 'error'

def checkToken(token):
    p = remote("127.0.0.1", 1337)
    p.recvuntil(b"Token :")
    p.sendline(token.encode())

    resp = p.clean()

    p.close()
    return resp

token = "218b43506e242a8be58b281d147a75f2709c2695687e6c42791677a4cc9e3a391873aeb156a1f1f3bbc0b9d1ab21d509687606b976351f4998d4210c0e875930"

blocks = [token[i:i+32] for i in range(0,len(token),32)]

# Starting the console status to indicate the progress
with console.status(f"Trying byte : ") as a:

    # Creating a string with 30 zeros, which corresponds to a block of 15 null bytes
    block_attack = "00" * 15

    # Extracting the last byte of the second-to-last block
    original_block_byte = bytes.fromhex(blocks[-2][-2:])
    print(f"Original block byte : {original_block_byte}")

    # Looping through all possible values of a byte (0-255)
    for c in range(255):
        
        # Converting the byte value to bytes
        c = bytes([c])

        # Replacing the second-to-last block of the ciphertext by the arbitrary forged block
        blocks[-2] = block_attack + c.hex()

        # Sending the modified ciphertext to the server to check if padding is valid
        resp = checkToken("".join(blocks))

        # Updating the console status to show the current byte value being tried
        a.update(f"Trying byte : {c}")

        # If the server does not return a padding error, the guessed byte is correct
        if b'Padding' not in resp and b"padding" not in resp:

            # Printing the result of the brute force
            print(f"bruteforce result : {c}")

            # XORing the guessed byte with the original byte to get the plaintext
            last_char = xor(c, original_block_byte, b"\x01")
            print(f"last char : {last_char}")

            # Exiting the loop as the last character has been found
            break
$ python3 example.py
Original block byte : b'\t'
bruteforce result : b'\x04'
last char : b'\x0c'

here the byte 0x0c was found, it means that there is probably a padding of size 13 on the plain text

Full exploitation

We just have to update the code to make it follow the entire padding oracle algorithm

from pwn import *
from rich.console import Console
import string
import json
import copy

console = Console()
context.log_level = 'error'

def getToken():

    p = remote("127.0.0.1", 1337)
    p.recvuntil(b": ")
    token = p.recvline()[:-1].decode()

    p.close()
    return token


def checkToken(token):
    p = remote("127.0.0.1", 1337)
    p.recvuntil(b"Token :")
    p.sendline(token.encode())

    resp = p.clean()

    p.close()
    return resp

token = bytes.fromhex(getToken())

blocks = [token[i:i+16] for i in range(0,len(token),16)]

plain = b""
with console.status(f"Trying byte : ") as a:
    for i in range(len(blocks)-1):
        arbitrary = copy.copy(blocks)
        for b in range(16):
            cur_plain = b"\x00" * (16-b) + plain[-16*(i+1):len(plain)-16*i]
            trail = xor(b+1, cur_plain, blocks[-2])[16-b:]
            
            for c in range(0,255):
                c = bytes([c])
                block_attack = (15 - b) * b'\x00' + c + trail
                arbitrary[-2] = block_attack
                test = b''.join(arbitrary).hex()

                a.update(f"clear = {plain}\nblock_attack = {block_attack}\ntoken = {test}\nTrying byte : {c}")
                r = checkToken(test)

                if b'Padding' not in r and b"padding" not in r:
                    plain_byte = xor(c, b+1, blocks[-2][-b-1])
                    plain = plain_byte + plain
                    break

        blocks = blocks[:-1]

print(f"clear = {plain}")
$ python3 exploit.py
⠏ clear = b'}"}\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
block_attack = b"\x08x\x93\xc6CS\x89\xfd'mY\xad\xe66!\xc2"
token = 08b9c82b87335577d4fc953594fd96ae612c3c8c0b29fc602bf2f08fac717aaf087893c6435389fd276d59ade63621c246a0379337609b70da2d2d0558326511
Trying byte : b'\x08'

...

⠏ clear = b'esome}"}\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
block_attack = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00U\xcc\x07\x10\xc7'
token = 08b9c82b87335577d4fc953594fd96ae000000000000000000000055cc0710c7c915a1ab5f4f95e13b7145b1fa2a3dde
Trying byte : b'U'

...

clear = b'{"flag": "FLAG{CBCOracleIsAwesome}"}\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'

Using Libraries

from pwn import *
from paddingoracle.paddingoracle import BadPaddingException, PaddingOracle
from Crypto.Cipher import AES

context.log_level = 'error'
class PadBuster(PaddingOracle):
    def oracle(self, data):
        while True:
            try:
                r = remote("127.0.0.1", 1337)
                r.recvuntil(b"Token : ")
                s = data.hex().encode()
                r.sendline(s)
                out = r.recvall()
                if b"padding" in out or b"Padding" in out:
                    raise BadPaddingException
                return
            except (socket.error, socket.gaierror, socket.herror, socket.timeout) as e:
                print(e)

if __name__ == '__main__':
    token = bytes.fromhex("c5bf8a9b3e59cd2d80e06a99bc36cbdefea51ee96ae74ac53ecc2e54da7a229e60b7cceacc4d8b96b490f68aa3a800975679289a314ea455c58516947899f216")
    padbuster = PadBuster()
    decrypted = padbuster.decrypt(token[16:], block_size=AES.block_size, iv=token[:16])

    print(f"Clear : {decrypted}")
$ python3 exploit_lib.py
Clear : bytearray(b'{"flag": "FLAG{CBCOracleIsAwesome}"}\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c')

Exercice

docker pull thectfrecipes/crypto:CBCOracle

Deploy the image using the followed command :

docker run --name thectfrecipes_crypto_CBCOracle -it --rm -d -p 1337:1337 thectfrecipes/crypto:CBCOracle

The service is available on port 1337

nc 127.0.0.1 1337
PreviousPadding oracleNextOFB

Last updated 2 years ago

As explained , in order to retrieve the last character of the last block, we need to brute force the last character of the penultimate block until the application returns a valid padding.

Many people have write libraries to automate this attack. Here is an example using python implementation of padBuster :

If you want to try this exploit by yourself, you can pull :

here
this
this docker image