RCTF 2019 has been held. We participated in it as team zer0pts
and were the 59th place with 791 points. I'm writing up about the baby crypto
challenge.
[Crypto 400pts(Solved by 31 teams)] baby crypto
nc 45.76.208.70 20000 nc 207.148.68.109 20000
Attachments:Click to download attachment 1
First, it was very similar to the decrypto challenge of BSidesSF 2019 CTF. Luckily I knew it, so I could get the second solve.
We were given the server side script file: crypto.py
.
#!/usr/bin/python3 -u from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from hashlib import sha1 import binascii import json import os import re import sys def pad(s): padder = padding.PKCS7(128).padder() return padder.update(s) + padder.finalize() def unpad(s): unpadder = padding.PKCS7(128).unpadder() return unpadder.update(s) + unpadder.finalize() key = os.urandom(16) iv = os.urandom(16) salt = key iv_len = 16 print("Input username:") username = sys.stdin.readline().strip() if not re.match("^[a-z]{5,10}$", username): print("Invalid username") exit() print("Input password:") password = sys.stdin.readline().strip() if not re.match("^[a-z]{5,10}$", password): print("Invalid password") exit() cookie = b"admin:0;username:%s;password:%s" %(username.encode(), password.encode()) h = sha1() h.update(salt) h.update(cookie) hv = h.digest() hv_hex = h.hexdigest() hash_len = len(hv) cookie_padded = pad(cookie) backend = default_backend() cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) encryptor = cipher.encryptor() cookie_padded_encrypted = encryptor.update(cookie_padded) + encryptor.finalize() print("Your cookie:") print(binascii.hexlify(iv).decode() + binascii.hexlify(cookie_padded_encrypted).decode() + hv_hex) def is_valid_hash(cookie, hv): h = sha1() h.update(salt) h.update(cookie) return hv == h.digest() while True: try: print("Input your cookie:") data_hex = sys.stdin.readline().strip() data = binascii.unhexlify(data_hex) assert(len(data) > iv_len + hash_len) iv, cookie_padded_encrypted, hv = data[:iv_len], data[iv_len: -hash_len], data[-hash_len:] cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) decryptor = cipher.decryptor() cookie_padded = decryptor.update(cookie_padded_encrypted) + decryptor.finalize() try: cookie = unpad(cookie_padded) except Exception as e: print("Invalid padding") continue if not is_valid_hash(cookie, hv): print("Invalid hash") continue info = {} for _ in cookie.split(b";"): k, v = _.split(b":") info[k] = v if info[b"admin"] == b"1": with open("flag") as f: flag = f.read() print("Your flag: %s" %flag) else: print("Goodbye nobody") exit() except Exception as e: print("Invalid cookie %s" %e)
The application manages a login credential encrypted with AES-CBC PCKS#7. As we can get the decryption oracle, the padding oracle encryption attack works.
However, a salted SHA-1 hash value of the login credential was also used for checking the consistency of the credential value. It prevents us from a naive padding oracle attack.
How can we avoid the check? The length extension attack is also available since we know the length of the salt and the heading several bytes of the plaintext.
Thus, to get the flag, we just have to create a new login credential which is known_plaintext || paddings || ;admin:1
, get its SHA-1 value by length extension attack, and then encrypt it using padding oracle encryption attack.
The following script does this.
from ptrlib import * from binascii import hexlify, unhexlify iv_len = 16 hash_len = 20 salt_len = 16 username = b"takoyaki" password = b"hogepiyo" sock = Socket("207.148.68.109", 20000) # sock = Socket("localhost", 8765) sock.recvuntil("Input username:\n") sock.sendline(username) sock.recvuntil("Input password:\n") sock.sendline(password) sock.recvuntil("Your cookie:\n") hex_cookie = sock.recvline().decode().strip() data = unhexlify(hex_cookie) iv, cookie, checksum = data[:iv_len], data[iv_len:-hash_len], data[-hash_len:] print("[+]cookie, checksum = {}, {}".format(cookie, checksum)) def oracle(x): sock.recvuntil("Input your cookie:\n") my_cookie = hexlify(x + checksum).decode() sock.sendline(my_cookie) result = sock.recvline().decode().strip() if "padding" in result: return False if "hash" in result: return True raise Exception(result) def pad(x): l = (16 - len(x)) % 16 return x + bytes([l] * l) known_msg = b"admin:0;username:%s;password:%s" % (username, password) append_msg = b";admin:1" new_checksum, new_data = lenext(SHA1, salt_len, checksum, known_msg, append_msg) attack_data = padding_oracle_encrypt(oracle, plain=pad(new_data), bs=16, unknown=b"A") print("[+]new_cookie, new_checksum = {}, {}".format(repr(new_data), repr(new_checksum))) print("[+]attack data = {}".format(hexlify(new_data))) sock.recvuntil("Input your cookie:\n") attack_cookie = hexlify(attack_data).decode() + new_checksum sock.sendline(attack_cookie) print(new_checksum) print(sock.recvline()) print(sock.recvline()) print(sock.recvline()) print(sock.recvline())