Added writeups
This commit is contained in:
129
variable_security.md
Normal file
129
variable_security.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 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}
|
||||
```
|
||||
Reference in New Issue
Block a user