Maleabilidad del encabezado LUKS2 y abuso de null-cipher en Confidential VMs
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
TL;DR
- Muchas Confidential VMs (CVMs) basadas en Linux que se ejecutan en AMD SEV-SNP o Intel TDX usan LUKS2 para almacenamiento persistente. El encabezado LUKS2 en disco es maleable y no está protegido por integridad frente a atacantes adyacentes al almacenamiento.
- Si el cifrado del segmento de datos en el encabezado se configura con un null cipher (p. ej., “cipher_null-ecb”), cryptsetup lo acepta y el guest lee/escribe en texto plano de forma transparente mientras cree que el disco está cifrado.
- Hasta cryptsetup 2.8.0 inclusive, los null ciphers podían usarse para keyslots; desde 2.8.1 se rechazan para keyslots con contraseñas no vacías, pero los null ciphers siguen permitidos para los segmentos de volumen.
- La remote attestation normalmente mide el código/configuración de la VM, no los encabezados LUKS externos y mutables; sin una validación/medición explícita, un atacante con acceso de escritura al disco puede forzar I/O en texto plano.
Antecedentes: formato en disco de LUKS2 (lo que importa para los atacantes)
- Un dispositivo LUKS2 comienza con un encabezado seguido de datos cifrados.
- El encabezado contiene dos copias idénticas de una sección binaria y una sección de metadatos JSON, además de uno o más keyslots.
- Los metadatos JSON definen:
- los keyslots habilitados y su KDF/cipher de envoltura
- los segmentos que describen el área de datos (cipher/mode)
- digests (p. ej., hash de la clave de volumen para verificar contraseñas)
- Valores típicos seguros: keyslot KDF argon2id; cifrado de keyslot y segmentos de datos aes-xts-plain64.
Inspeccione rápidamente el cipher del segmento directamente desde el 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'
Causa raíz
- Los LUKS2 headers no están autenticados contra manipulaciones del almacenamiento. Un atacante del host/almacenamiento puede reescribir los metadatos JSON aceptados por cryptsetup.
- Desde cryptsetup 2.8.0, se aceptan headers que fijan la encriptación de un segmento a cipher_null-ecb. El null cipher ignora las claves y devuelve texto en claro.
- Hasta la 2.8.0, los null ciphers también podían usarse para keyslots (el keyslot se abre con cualquier passphrase). Desde 2.8.1, los null ciphers se rechazan para keyslots con passwords no vacías, pero siguen permitidos para segmentos. Cambiar solo el cipher del segmento sigue produciendo I/O en texto en claro después de 2.8.1.
Modelo de amenaza: por qué la attestation no te salvó por defecto
- CVMs buscan asegurar confidencialidad, integridad y autenticidad en un host no confiable.
- Remote attestation normalmente mide la imagen de la VM y la configuración de lanzamiento, no el header LUKS mutable que vive en almacenamiento no confiable.
- Si tu CVM confía en un header en disco sin validación/medición robusta, un atacante del almacenamiento puede alterarlo a un null cipher y tu guest montará un volumen en texto en claro sin error.
Explotación (se requiere acceso de escritura al almacenamiento)
Precondiciones:
- Acceso de escritura al dispositivo de bloque cifrado LUKS2 del CVM.
- El guest usa el header LUKS2 en disco sin validación/attestation robusta.
Pasos (a alto nivel):
- Leer el JSON del header e identificar la definición del segmento de datos. Campo de ejemplo: segments[“0”].encryption.
- Ajustar la encriptación del segmento de datos a un null cipher, p.ej., cipher_null-ecb. Mantener los parámetros del keyslot y la estructura de digest intactos para que la passphrase habitual del guest siga “funcionando”.
- Actualizar ambas copias del header y los digest asociados para que el header sea autoconsistente.
- En el siguiente arranque, el guest ejecuta cryptsetup, desbloquea con éxito el keyslot existente usando su passphrase y monta el volumen. Debido a que el cipher del segmento es un null cipher, todas las lecturas/escrituras son en texto en claro.
Variante (abuso de keyslot pre-2.8.1): si el area.encryption de un keyslot es un null cipher, se abre con cualquier passphrase. Combínalo con un null segment cipher para acceso en texto en claro sin conocer el secreto del guest.
Mitigaciones robustas (evitar TOCTOU con detached headers)
Siempre trata los LUKS headers en disco como entrada no confiable. Usa detached-header mode para que la validación y la apertura usen los mismos bytes de confianza procedentes de la RAM protegida:
# 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
A continuación, aplique una (o más) de las siguientes medidas:
- Aplicar un MAC al header completo
- Calcular/verificar un MAC sobre todo el header antes de su uso.
- Abrir el volume solo cuando el MAC verifique.
- Ejemplos en la práctica: Flashbots tdx-init y Fortanix Salmiac adoptaron verificación basada en MAC.
- Validación estricta de JSON (compatible hacia atrás)
- Volcar los metadatos JSON y validar una lista blanca estricta de parámetros (KDF, ciphers, recuento/tipo de segmentos, 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
Validador de ejemplo (hacer cumplir campos seguros)
```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") ```- Medir/atestiguar la cabecera
- Eliminar salts/digests aleatorios y medir la cabecera saneada en los PCRs de TPM/TDX/SEV o en el estado de política de KMS.
- Liberar las claves de descifrado solo cuando la cabecera medida coincida con un perfil aprobado y seguro.
Orientación operativa:
- Aplicar detached header + MAC o validación estricta; nunca confiar directamente en on-disk headers.
- Los consumidores de attestation deberían denegar versiones del framework previas al parche en las allow-lists.
Notas sobre versiones y la postura del mantenedor
- Los mantenedores de cryptsetup aclararon que LUKS2 no fue diseñado para proporcionar integridad frente a la manipulación del almacenamiento en este contexto; null ciphers se mantienen por compatibilidad hacia atrás.
- cryptsetup 2.8.1 (Oct 19, 2025) rechaza null ciphers para keyslots con contraseñas no vacías pero aún permite null ciphers para segments.
Comprobaciones rápidas y triaje
- Inspeccionar si la encriptación de algún segmento está configurada con un null cipher:
cryptsetup luksDump --type luks2 --dump-json-metadata /dev/VDISK \
| jq -r '.segments | to_entries[] | "segment=" + .key + ", enc=" + .value.encryption'
- Verifica los algoritmos de keyslot y segment antes de abrir el volumen. Si no puedes comprobar el MAC, aplica una validación JSON estricta y ábrelo usando el detached header desde protected memory.
Referencias
- Vulnerabilities in LUKS2 disk encryption for confidential VMs (Trail of Bits)
- cryptsetup issue #954 (null cipher acceptance and integrity considerations)
- CVE-2025-59054
- CVE-2025-58356
- Related context: CVE-2021-4122 (auto-recovery path silently decrypting disks)
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks Cloud

