# Variable Security `Variable Security` is a cryptography challenge that exploits a vulnerability in a custom HMAC signing service. The service allows users to generate HMAC-SHA256 signatures for arbitrary messages using a secret key (the flag). Crucially, the service provides a "Key Optimization Level" feature, which lets the user specify how many bytes of the secret key should be used for signing. ## Information Gathering We are presented with a web interface titled "SecureSign Enterprise". It offers a form with two fields: 1. **Message Content**: Text input for the message to be signed. 2. **Key Optimization Level**: A numeric input specifying the length of the key to use. From the challenge description ("We allow clients to adjust the 'Key Optimization' level") and the form field `key_len`, we can infer the backend logic behaves something like this: ```python # Inferred Logic # key_len comes from user input current_key = SECRET_KEY[:key_len] # Vulnerability: Uses only first N bytes h = hmac.new(current_key, message.encode(), hashlib.sha256) signature = h.hexdigest() ``` The application creates an HMAC signature using a slice of the secret key determined by the user input `key_len`. This allows us to sign messages using only the first $N$ bytes of the flag. ## The Vulnerability This setup creates an **Oracle** that leaks information about the key byte-by-byte. The vulnerability lies in the fact that we can control exactly how much of the unknown key is used in the cryptographic operation. This allows us to break the problem of finding the entire key into finding it one character at a time. Here is the strategy: **1. Finding the first byte:** We don't know the key, but we can ask the server to sign a message (e.g., "test") using only **1 byte** of the key (`key_len=1`). * The server computes `HMAC(key[0], "test")` and returns the signature. * We can replicate this locally! We try every possible character (A, B, C...) as the key. * We compute `HMAC("A", "test")`, `HMAC("B", "test")`, etc. * When our local signature matches the server's signature, we know we have found the first byte of the secret key. **2. Finding the second byte:** Now that we know the first byte (let's say it's `{`), we ask the server to sign "test" using **2 bytes** of the key (`key_len=2`). * The server computes `HMAC("{?", "test")`. * We again brute-force the unknown second character locally. We try `{A`, `{B`, `{C`... * We compute `HMAC("{A", "test")`, `HMAC("{B", "test")`... * The match reveals the second byte. **3. Repeat:** We continue this process for `key_len=3`, `key_len=4`, and so on, until we have recovered the entire flag. ## Solution We can write a script to automate this byte-by-byte brute-force attack. ### Solver Script ```python import requests import hmac import hashlib import string import re # Configuration TARGET_URL = "http://127.0.0.1:5000" # Adjust as needed MESSAGE = "test" MAX_LEN = 33 # Maximum length of the flag (inferred or found via trial) def get_signature(length): """Request signature from server with specific key length.""" try: resp = requests.post(TARGET_URL, data={'message': MESSAGE, 'key_len': length}) # Extract hex signature from HTML response match = re.search(r'([a-f0-9]{64})', resp.text) return match.group(1) if match else None except: return None def solve(): known_flag = b"" print(f"[*] Starting attack on {TARGET_URL}...") # Iterate through each byte position for length in range(1, MAX_LEN + 1): # 1. Get the target signature from the server # This signature is generated using the real first 'length' bytes of the flag target_sig = get_signature(length) if not target_sig: print(f"[-] Failed to get signature for length {length}") break # 2. Brute-force the next character found = False # Try all printable characters for char_code in string.printable.encode(): char = bytes([char_code]) # Construct our guess: The part we already know + the new character we are testing candidate_key = known_flag + char # Calculate HMAC locally using our guess local_sig = hmac.new(candidate_key, MESSAGE.encode(), hashlib.sha256).hexdigest() # If the signatures match, our guess for the new character is correct if local_sig == target_sig: known_flag += char print(f"[+] Byte {length}: {char.decode()} -> {known_flag.decode()}") found = True break if not found: print("[-] Character not found in printable range.") break print(f"\n[!] Final Flag: {known_flag.decode()}") if __name__ == "__main__": solve() ``` ### Execution Running the script recovers the flag character by character: ```bash $ python3 solve.py [*] Starting attack on http://127.0.0.1:5000... [+] Byte 1: { -> { [+] Byte 2: f -> {f [+] Byte 3: l -> {fl ... [+] Byte 32: } -> {flag: byte_by_byte_we_get_rich} [!] Final Flag: {flag: byte_by_byte_we_get_rich} ```