Files
HIP7CTF_Writeups/variable_security.md
m0rph3us1987 a79656b647 Added writeups
2026-03-08 12:22:39 +01:00

5.1 KiB

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:

# 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

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:

$ 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}