Maleabilidade do Cabeçalho LUKS2 e Abuso de Null-Cipher em Confidential VMs
Reading time: 8 minutes
tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
TL;DR
- Muitos Confidential VMs (CVMs) baseados em Linux rodando em AMD SEV-SNP ou Intel TDX usam LUKS2 para armazenamento persistente. O cabeçalho LUKS2 no disco é maleável e não tem proteção de integridade contra atacantes adjacentes ao armazenamento.
- Se a encriptação do segmento de dados no cabeçalho for configurada para um null cipher (e.g., "cipher_null-ecb"), o cryptsetup aceita isso e o guest lê/escreve plaintext de forma transparente enquanto acredita que o disco está encriptado.
- Antes e inclusive o cryptsetup 2.8.0, null ciphers podiam ser usados para keyslots; desde 2.8.1 eles são rejeitados para keyslots com passwords não vazias, mas null ciphers continuam permitidos para volume segments.
- Remote attestation geralmente mede código/config da VM, não cabeçalhos LUKS externos mutáveis; sem validação/medição explícita, um atacante com acesso de escrita ao disco pode forçar I/O em plaintext.
Contexto: formato on-disk do LUKS2 (o que importa para atacantes)
- Um dispositivo LUKS2 começa com um cabeçalho seguido pelos dados encriptados.
- O cabeçalho contém duas cópias idênticas de uma seção binária e uma seção de metadata em JSON, além de um ou mais keyslots.
- A metadata JSON define:
- keyslots habilitados e seu KDF/cipher de wrapping
- segments que descrevem a área de dados (cipher/mode)
- digests (e.g., hash do volume key para verificar passphrases)
- Valores típicos seguros: keyslot KDF argon2id; keyslot e encriptação do data segment aes-xts-plain64.
Inspecione rapidamente o cipher do segmento diretamente no 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 raiz
- Os cabeçalhos LUKS2 não são autenticados contra adulteração do armazenamento. Um atacante do host/armazenamento pode reescrever os metadados JSON aceitos pelo cryptsetup.
- A partir do cryptsetup 2.8.0, cabeçalhos que definem a encriptação de um segmento para cipher_null-ecb são aceitos. O null cipher ignora chaves e retorna plaintext.
- Até 2.8.0, null ciphers também podiam ser usados para keyslots (o keyslot abria com qualquer passphrase). Desde 2.8.1, null ciphers são rejeitados para keyslots com senhas não vazias, mas continuam permitidos para segmentos. Alterar apenas o cipher do segmento ainda resulta em I/O em plaintext após 2.8.1.
Modelo de ameaça: por que attestation não te salvou por padrão
- CVMs visam garantir confidencialidade, integridade e autenticidade em um host não confiável.
- Remote attestation normalmente mede a imagem da VM e a configuração de inicialização, não o header LUKS mutável que vive em armazenamento não confiável.
- Se sua CVM confia em um header on-disk sem validação/attestation robusta, um atacante de armazenamento pode alterá-lo para um null cipher e seu guest montará um volume em plaintext sem erro.
Exploração (acesso de escrita ao armazenamento exigido)
Pré-condições:
- Acesso de escrita ao dispositivo de bloco encriptado LUKS2 do CVM.
- O guest usa o header LUKS2 em disco sem validação/attestation robusta.
Passos (visão geral):
- Leia o JSON do header e identifique a definição do segmento de dados. Campo de exemplo: segments["0"].encryption.
- Defina a encriptação do segmento de dados para um null cipher, p.ex., cipher_null-ecb. Mantenha os parâmetros do keyslot e a estrutura de digest intactos para que a passphrase usual do guest ainda “funcione.”
- Atualize ambas as cópias do header e os digests associados para que o header seja autoconsistente.
- No próximo boot, o guest executa o cryptsetup, desbloqueia com sucesso o keyslot existente com sua passphrase e monta o volume. Como o cipher do segmento é um null cipher, todas as leituras/escritas são em plaintext.
Variante (abuso de keyslot pré-2.8.1): se a area.encryption do keyslot for um null cipher, ele abre com qualquer passphrase. Combine com um null cipher no segmento para acesso em plaintext sem saber o segredo do guest.
Mitigações robustas (evite TOCTOU com detached headers)
Sempre trate os LUKS headers em disco como entrada não confiável. Use detached-header mode para que a validação e a abertura usem os mesmos bytes confiáveis vindos de 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
Então aplique uma (ou mais) das seguintes:
- Aplicar MAC ao cabeçalho completo
- Calcular/verificar um MAC sobre todo o cabeçalho antes do uso.
- Abrir o volume apenas quando o MAC for verificado.
- Exemplos no mundo real: Flashbots tdx-init e Fortanix Salmiac adotaram verificação baseada em MAC.
- Validação estrita de JSON (compatível com versões anteriores)
- Extrair os metadados JSON e validar uma allowlist estrita de parâmetros (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
Exemplo de validador (garantir campos seguros)
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/atestar o cabeçalho
- Remova salts/digests aleatórios e meça o cabeçalho sanitizado nos PCRs do TPM/TDX/SEV ou no estado da política do KMS.
- Libere as chaves de decriptação apenas quando o cabeçalho medido corresponder a um perfil aprovado e seguro.
Orientação operacional:
- Imponha detached header + MAC ou validação estrita; nunca confie diretamente nos cabeçalhos em disco.
- Consumidores de attestation devem negar versões de framework pré-patch nas allow-lists.
Notas sobre versões e posição do mantenedor
- Os mantenedores do cryptsetup esclareceram que o LUKS2 não foi projetado para fornecer integridade contra adulteração do armazenamento neste contexto; null ciphers são mantidos por compatibilidade com versões anteriores.
- cryptsetup 2.8.1 (Oct 19, 2025) rejeita null ciphers para keyslots com senhas não vazias, mas ainda permite null ciphers para segmentos.
Verificações rápidas e triagem
- Inspecione se alguma criptografia de segmento está configurada para um null cipher:
cryptsetup luksDump --type luks2 --dump-json-metadata /dev/VDISK \
| jq -r '.segments | to_entries[] | "segment=" + .key + ", enc=" + .value.encryption'
- Verifique os algoritmos de keyslot e de segmento antes de abrir o volume. Se não puder aplicar MAC, exija validação JSON rigorosa e abra usando o detached header da memória protegida.
Referências
- 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
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
HackTricks Cloud