LUKS2 Header Malleability and Null-Cipher Abuse in Confidential VMs

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

TL;DR

  • Viele Linux-basierte Confidential VMs (CVMs), die auf AMD SEV-SNP oder Intel TDX laufen, verwenden LUKS2 für persistenten Speicher. Der on-disk LUKS2-Header ist malleable und nicht gegen storage-adjacent Angreifer integritätsgeschützt.
  • Wenn die Verschlüsselung des Header-Datensegments auf einen null cipher gesetzt ist (z. B. “cipher_null-ecb”), akzeptiert cryptsetup das und der Guest liest/schreibt transparent Klartext, während er glaubt, die Festplatte sei verschlüsselt.
  • Bis einschließlich cryptsetup 2.8.0 konnten null ciphers für keyslots verwendet werden; seit 2.8.1 werden sie für keyslots mit nicht-leerem Passwort abgelehnt, aber null ciphers sind weiterhin für volume segments erlaubt.
  • Remote attestation misst üblicherweise VM-Code/Config, nicht veränderliche externe LUKS-Header; ohne explizite Validierung/Measurement kann ein Angreifer mit Schreibzugriff auf die Disk Klartext-I/O erzwingen.

Background: LUKS2 on-disk format (what matters for attackers)

  • Ein LUKS2-Device beginnt mit einem Header, gefolgt von verschlüsselten Daten.
  • Der Header enthält zwei identische Kopien eines binären Abschnitts und einen JSON-Metadatenabschnitt sowie einen oder mehrere keyslots.
  • Die JSON-Metadaten definieren:
    • welche keyslots aktiviert sind und deren wrapping KDF/cipher
    • segments, die den Datenbereich beschreiben (cipher/mode)
    • digests (z. B. Hash des volume key zur Verifikation von Passphrasen)
  • Typische sichere Werte: keyslot KDF argon2id; keyslot- und data segment-Verschlüsselung aes-xts-plain64.

Quickly inspect the segment cipher directly from JSON:

# Read JSON metadata and print the configured data segment cipher
cryptsetup luksDump --type luks2 --dump-json-metadata /dev/VDISK \
| jq -r '.segments["0"].encryption'

Root cause

  • LUKS2-Header sind nicht gegen Manipulationen des Speichers authentifiziert. Ein Host-/Storage-Angreifer kann die von cryptsetup akzeptierten JSON-Metadaten umschreiben.
  • Seit cryptsetup 2.8.0 werden Header akzeptiert, die die Verschlüsselung eines Segments auf cipher_null-ecb setzen. Der null cipher ignoriert Schlüssel und liefert Klartext zurück.
  • Bis einschließlich 2.8.0 konnten null ciphers auch für keyslots verwendet werden (ein keyslot öffnet sich mit jeder Passphrase). Seit 2.8.1 werden null ciphers für keyslots mit nicht-leeren Passwörtern abgelehnt, bleiben aber für Segmente erlaubt. Allein das Ändern des Segment-Ciphers führt auch nach 2.8.1 weiterhin zu Klartext-I/O.

Threat model: why attestation didn’t save you by default

  • CVMs sollen Vertraulichkeit, Integrität und Authentizität in einem nicht vertrauenswürdigen Host sicherstellen.
  • Remote attestation misst üblicherweise das VM-Image und die Launch-Konfiguration, nicht den veränderlichen LUKS-Header, der auf nicht vertrauenswürdigem Speicher liegt.
  • Wenn deine CVM einem on-disk Header ohne robuste Validierung/attestation vertraut, kann ein Storage-Angreifer ihn auf einen null cipher ändern und dein Guest wird ein Klartext-Volume ohne Fehler mounten.

Exploitation (storage write access required)

Preconditions:

  • Schreibzugriff auf das LUKS2-verschlüsselte Blockgerät der CVM.
  • Der Guest verwendet den on-disk LUKS2-Header ohne robuste Validierung/attestation.

Steps (high level):

  1. Lese das Header-JSON und identifiziere die Definition des Datensegments. Beispielziel-Feld: segments[“0”].encryption.
  2. Setze die Verschlüsselung des Datensegments auf einen null cipher, z.B. cipher_null-ecb. Belasse keyslot-Parameter und Digest-Struktur unverändert, sodass die übliche Passphrase des Guests weiterhin „funktioniert“.
  3. Aktualisiere beide Header-Kopien und die zugehörigen Header-Digests, sodass der Header selbstkonsistent ist.
  4. Beim nächsten Boot führt der Guest cryptsetup aus, entsperrt erfolgreich den vorhandenen keyslot mit seiner Passphrase und mountet das Volume. Da der Segment-Cipher ein null cipher ist, sind alle Lese-/Schreibvorgänge Klartext.

Variant (pre-2.8.1 keyslot abuse): wenn area.encryption eines keyslots ein null cipher ist, öffnet er sich mit jeder Passphrase. Kombiniere das mit einem null segment cipher für nahtlosen Klartext-Zugriff, ohne das Guest-Secret zu kennen.

Robust mitigations (avoid TOCTOU with detached headers)

Behandle on-disk LUKS-Header stets als nicht vertrauenswürdige Eingabe. Verwende detached-header mode, damit Validierung und Öffnen dieselben vertrauenswürdigen Bytes aus geschütztem RAM verwenden:

# Copy header into protected memory (e.g., tmpfs) and open from there
cryptsetup luksHeaderBackup --header-backup-file /tmp/luks_header /dev/VDISK
cryptsetup open --type luks2 --header /tmp/luks_header /dev/VDISK --key-file=key.txt

Dann erzwinge eine (oder mehrere) der folgenden Maßnahmen:

  1. Den gesamten Header mit einem MAC sichern
  • Berechne/verifiziere vor der Verwendung einen MAC über den gesamten Header.
  • Öffne das Volume nur, wenn der MAC verifiziert ist.
  • Beispiele in der Praxis: Flashbots tdx-init und Fortanix Salmiac nutzen MAC-basierte Verifikation.
  1. Strikte JSON-Validierung (abwärtskompatibel)
  • Dump JSON-Metadaten und validiere eine strikte allowlist von Parametern (KDF, ciphers, segment count/type, flags).
#!/bin/bash
set -e
# Store header in confidential RAM fs
cryptsetup luksHeaderBackup --header-backup-file /tmp/luks_header $BLOCK_DEVICE
# Dump JSON metadata header to a file
cryptsetup luksDump --type luks2 --dump-json-metadata /tmp/luks_header > header.json
# Validate the header
python validate.py header.json
# Open the cryptfs using key.txt
cryptsetup open --type luks2 --header /tmp/luks_header $BLOCK_DEVICE --key-file=key.txt
Beispiel-Validator (sichere Felder erzwingen) ```python from json import load import sys with open(sys.argv[1], "r") as f: header = load(f) if len(header["keyslots"]) != 1: raise ValueError("Expected 1 keyslot") if header["keyslots"]["0"]["type"] != "luks2": raise ValueError("Expected luks2 keyslot") if header["keyslots"]["0"]["area"]["encryption"] != "aes-xts-plain64": raise ValueError("Expected aes-xts-plain64 encryption") if header["keyslots"]["0"]["kdf"]["type"] != "argon2id": raise ValueError("Expected argon2id kdf") if len(header["tokens"]) != 0: raise ValueError("Expected 0 tokens") if len(header["segments"]) != 1: raise ValueError("Expected 1 segment") if header["segments"]["0"]["type"] != "crypt": raise ValueError("Expected crypt segment") if header["segments"]["0"]["encryption"] != "aes-xts-plain64": raise ValueError("Expected aes-xts-plain64 encryption") if "flags" in header["segments"]["0"] and header["segments"]["0"]["flags"]: raise ValueError("Segment contains unexpected flags") ```
  1. Header messen/attestieren
  • Entferne zufällige Salts/Digests und messe den bereinigten Header in TPM/TDX/SEV PCRs oder KMS policy state.
  • Gib Entschlüsselungskeys nur frei, wenn der gemessene Header einem genehmigten, sicheren Profil entspricht.

Operational guidance:

  • Enforce detached header + MAC oder strenge Validierung; vertraue niemals direkt auf on-disk Header.
  • Empfänger von Attestierungen sollten pre-patch Framework-Versionen in allow-lists ablehnen.

Hinweise zu Versionen und Position der Maintainer

  • Die Maintainer von cryptsetup haben klargestellt, dass LUKS2 nicht dafür ausgelegt ist, Integrität gegen Manipulationen des Speichers in diesem Kontext bereitzustellen; null ciphers werden zur Abwärtskompatibilität beibehalten.
  • cryptsetup 2.8.1 (Oct 19, 2025) lehnt null ciphers für keyslots mit nicht-leeren Passwörtern ab, erlaubt aber weiterhin null ciphers für segments.

Schnelle Checks und Triage

  • Prüfe, ob die Segmentverschlüsselung auf einen null cipher gesetzt ist:
cryptsetup luksDump --type luks2 --dump-json-metadata /dev/VDISK \
| jq -r '.segments | to_entries[] | "segment=" + .key + ", enc=" + .value.encryption'
  • Überprüfen Sie keyslot- und Segment-Algorithmen, bevor Sie das Volume öffnen. Wenn Sie MAC nicht durchführen können, erzwingen Sie eine strikte JSON-Validierung und öffnen Sie das Volume mit dem getrennten Header aus dem geschützten Speicher.

Referenzen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks