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

5.8 KiB

Variable Security

Variable Security ist eine Kryptographie-Challenge, die eine Schwachstelle in einem benutzerdefinierten HMAC-Signierdienst ausnutzt. Der Dienst ermöglicht es Benutzern, HMAC-SHA256-Signaturen für beliebige Nachrichten unter Verwendung eines geheimen Schlüssels (der Flagge) zu generieren. Entscheidend ist, dass der Dienst eine Funktion "Key Optimization Level" bietet, mit der der Benutzer festlegen kann, wie viele Bytes des geheimen Schlüssels für das Signieren verwendet werden sollen.

Informationsbeschaffung

Uns wird eine Webinterface namens "SecureSign Enterprise" präsentiert. Es bietet ein Formular mit zwei Feldern:

  1. Message Content: Texteingabe für die zu signierende Nachricht.
  2. Key Optimization Level: Eine numerische Eingabe, die die Länge des zu verwendenden Schlüssels angibt.

Aus der Challenge-Beschreibung ("Wir erlauben Clients, das 'Key Optimization'-Level anzupassen") und dem Formularfeld key_len können wir ableiten, dass die Backend-Logik etwa so aussieht:

# Abgeleitete Logik
# key_len kommt von der Benutzereingabe
current_key = SECRET_KEY[:key_len]  # Schwachstelle: Verwendet nur die ersten N Bytes
h = hmac.new(current_key, message.encode(), hashlib.sha256)
signature = h.hexdigest()

Die Anwendung erstellt eine HMAC-Signatur unter Verwendung eines Teilstücks des geheimen Schlüssels, bestimmt durch die Benutzereingabe key_len. Dies ermöglicht es uns, Nachrichten unter Verwendung von nur den ersten N Bytes der Flagge zu signieren.

Die Schwachstelle

Dieses Setup erstellt ein Orakel, das Informationen über den Schlüssel Byte für Byte preisgibt. Die Schwachstelle liegt in der Tatsache, dass wir genau steuern können, wie viel des unbekannten Schlüssels in der kryptographischen Operation verwendet wird. Dies ermöglicht es uns, das Problem, den gesamten Schlüssel zu finden, darauf herunterzubrechen, ihn Zeichen für Zeichen zu finden.

Hier ist die Strategie:

1. Finden des ersten Bytes: Wir kennen den Schlüssel nicht, aber wir können den Server bitten, eine Nachricht (z.B. "test") unter Verwendung von nur 1 Byte des Schlüssels zu signieren (key_len=1).

  • Der Server berechnet HMAC(key[0], "test") und gibt die Signatur zurück.
  • Wir können dies lokal replizieren! Wir versuchen jedes mögliche Zeichen (A, B, C...) als Schlüssel.
  • Wir berechnen HMAC("A", "test"), HMAC("B", "test"), usw.
  • Wenn unsere lokale Signatur mit der Signatur des Servers übereinstimmt, wissen wir, dass wir das erste Byte des geheimen Schlüssels gefunden haben.

2. Finden des zweiten Bytes: Jetzt, da wir das erste Byte kennen (sagen wir, es ist {), bitten wir den Server, "test" unter Verwendung von 2 Bytes des Schlüssels zu signieren (key_len=2).

  • Der Server berechnet HMAC("{?", "test").
  • Wir führen wieder Brute-Force für das unbekannte zweite Zeichen lokal durch. Wir versuchen {A, {B, {C...
  • Wir berechnen HMAC("{A", "test"), HMAC("{B", "test")...
  • Die Übereinstimmung enthüllt das zweite Byte.

3. Wiederholen: Wir setzen diesen Prozess für key_len=3, key_len=4 usw. fort, bis wir die gesamte Flagge wiederhergestellt haben.

Lösung

Wir können ein Skript schreiben, um diesen Byte-für-Byte-Brute-Force-Angriff zu automatisieren.

Solver-Skript

import requests
import hmac
import hashlib
import string
import re

# Konfiguration
TARGET_URL = "http://127.0.0.1:5000"  # Nach Bedarf anpassen
MESSAGE = "test"
MAX_LEN = 33 # Maximale Länge der Flagge (abgeleitet oder durch Ausprobieren gefunden)

def get_signature(length):
    """Fordere Signatur vom Server mit spezifischer Schlüssellänge an."""
    try:
        resp = requests.post(TARGET_URL, data={'message': MESSAGE, 'key_len': length})
        # Extrahiere Hex-Signatur aus der HTML-Antwort
        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}...")

    # Iteriere durch jede Byte-Position
    for length in range(1, MAX_LEN + 1):
        # 1. Hole die Zielsignatur vom Server
        # Diese Signatur wird unter Verwendung der echten ersten 'length' Bytes der Flagge generiert
        target_sig = get_signature(length)
        if not target_sig:
            print(f"[-] Failed to get signature for length {length}")
            break

        # 2. Brute-Force das nächste Zeichen
        found = False
        # Versuche alle druckbaren Zeichen
        for char_code in string.printable.encode(): 
            char = bytes([char_code])
            
            # Konstruiere unseren Rateversuch: Der Teil, den wir schon kennen + das neue Zeichen, das wir testen
            candidate_key = known_flag + char
            
            # Berechne HMAC lokal mit unserem Rateversuch
            local_sig = hmac.new(candidate_key, MESSAGE.encode(), hashlib.sha256).hexdigest()
            
            # Wenn die Signaturen übereinstimmen, ist unser Rateversuch für das neue Zeichen korrekt
            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()

Ausführung

Das Ausführen des Skripts stellt die Flagge Zeichen für Zeichen wieder her:

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