An encryption service encrypts plaintext, but blocks encryption of the impossible_flag_user string. Exploit the ECB mode implementation to forge an encrypted token that decrypts to this forbidden value.
Vulnerability
The service uses AES in ECB mode, which has critical weakness: identical plaintext blocks produce identical ciphertext blocks. By crafting specific payloads, we can:
- Encrypt the first 16 bytes of the target string
- Encrypt padding-aligned subsequent bytes
- Concatenate the cipher blocks to forge a valid token
Exploit Strategy
The target user is: impossible_flag_user (20 bytes)
- Block 1: Encrypt
impossible_flag_(16 bytes) → get first cipher block - Block 2: Encrypt
user+ PKCS#7 padding → get second cipher block - Combine: Concatenate blocks to form forged token → decrypt equals target
This construction assumes /encrypt accepts hex-encoded plaintext and encrypts caller-provided bytes under the same ECB key across both requests. If it also applies PKCS#7, its extra padding block is discarded. The local check covers block layout and unpadding only; the service and endpoint parsing have not been rerun.
Solution
import binasciiimport requests
BASE_URL = "https://2c5a74f7cc3f1fdf.247ctf.com"
def solve(): # Block 1: First 16 bytes of target part1_plain = b"impossible_flag_" payload1 = binascii.hexlify(part1_plain).decode() r1 = requests.get(f"{BASE_URL}/encrypt", params={"user": payload1}, timeout=10) r1.raise_for_status() cipher_part1 = bytes.fromhex(r1.text.strip())[:16].hex() if len(cipher_part1) != 32: raise ValueError("Incomplete first ciphertext block") # 32 hex chars = 16 bytes print(f"[*] Block 1 Cipher: {cipher_part1}")
# 剩余 4 字节,PKCS#7 补 12 个 0x0c;仅取该块的密文 tail = b"user" padding_len = 16 - len(tail) part2_plain = tail + bytes([padding_len]) * padding_len payload2 = binascii.hexlify(part2_plain).decode() r2 = requests.get(f"{BASE_URL}/encrypt", params={"user": payload2}, timeout=10) r2.raise_for_status() cipher_part2 = bytes.fromhex(r2.text.strip())[:16].hex() if len(cipher_part2) != 32: raise ValueError("Incomplete second ciphertext block") print(f"[*] Block 2 Cipher: {cipher_part2}")
# Forge token by concatenating blocks final_token = cipher_part1 + cipher_part2 print(f"[*] Forged Token: {final_token}")
# Get flag r_flag = requests.get(f"{BASE_URL}/get_flag", params={"user": final_token}, timeout=10) r_flag.raise_for_status() print(f"\n---> FLAG: {r_flag.text}")
if __name__ == "__main__": solve()The returned flag belongs to the launched service instance; no cross-instance invariance is established. Record the generation/retrieval method and use the current instance response instead of a fixed-answer spoiler.