LUKS2 헤더 변경 가능성 및 Null-Cipher 악용 (Confidential VMs)

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

요약

  • 많은 Linux 기반 Confidential VMs (CVMs)이 AMD SEV-SNP 또는 Intel TDX에서 실행되며 영구 스토리지에 LUKS2를 사용한다. 디스크 상의 LUKS2 헤더는 변경 가능하며 스토리지 인접 공격자에 대해 무결성 보호가 되어 있지 않다.
  • 헤더의 데이터 세그먼트 암호가 null cipher(e.g., “cipher_null-ecb”)로 설정되어 있으면 cryptsetup은 이를 허용하고 게스트는 디스크가 암호화되어 있다고 믿는 동안 평문을 투명하게 읽고/쓴다.
  • cryptsetup 2.8.0 이전 및 해당 버전까지는 null ciphers를 keyslots에 사용할 수 있었고, 2.8.1 이후에는 비어 있지 않은 비밀번호가 있는 keyslots에 대해 거부되지만 null ciphers는 볼륨 세그먼트에는 여전히 허용된다.
  • Remote attestation은 일반적으로 VM 코드/구성만 측정하며 변경 가능한 외부 LUKS 헤더를 측정하지 않는다; 명시적 검증/측정이 없으면 디스크 쓰기 권한이 있는 공격자가 평문 I/O를 강제할 수 있다.

배경: LUKS2 온디스크 포맷 (공격자에게 중요한 부분)

  • LUKS2 장치는 헤더로 시작하고 그 뒤에 암호화된 데이터가 온다.
  • 헤더는 바이너리 섹션의 동일한 복사본 두 개와 JSON 메타데이터 섹션, 그리고 하나 이상의 keyslots를 포함한다.
  • JSON 메타데이터는 다음을 정의한다:
    • 활성화된 keyslots와 그들을 감싸는 KDF/cipher
    • 데이터 영역을 설명하는 segments (cipher/mode)
    • digests (예: 암호문 확인을 위한 볼륨 키의 해시)
  • 일반적인 안전 설정: keyslot KDF는 argon2id; keyslot 및 데이터 세그먼트 암호화는 aes-xts-plain64.

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'

근본 원인

  • LUKS2 헤더는 스토리지 변조에 대해 인증되지 않습니다. 호스트/스토리지 공격자는 cryptsetup이 수용하는 JSON 메타데이터를 재작성할 수 있습니다.
  • cryptsetup 2.8.0부터, 세그먼트의 암호화를 cipher_null-ecb로 설정한 헤더가 허용됩니다. The null cipher는 키를 무시하고 평문을 반환합니다.
  • 2.8.0까지는 null ciphers를 keyslots에도 사용할 수 있었고 (keyslot은 어떤 패스프레이즈로든 열림). 2.8.1부터는 비어있지 않은 비밀번호가 있는 keyslots에 대해 null ciphers가 거부되지만, segments에 대해서는 여전히 허용됩니다. segment cipher만 변경해도 2.8.1 이후에도 평문 I/O가 발생합니다.

위협 모델: 왜 attestation이 기본적으로 보호하지 못했는가

  • CVMs는 신뢰할 수 없는 호스트에서 기밀성, 무결성, 및 진위성을 보장하는 것을 목표로 합니다.
  • Remote attestation은 보통 VM 이미지와 런치 구성(launch configuration)을 측정하며, 신뢰할 수 없는 스토리지에 있는 변경 가능한 LUKS 헤더는 측정하지 않습니다.
  • 만약 CVM이 충분한 검증/측정 없이 온디스크 헤더를 신뢰한다면, 스토리지 공격자는 이를 null cipher로 변경할 수 있고 guest는 오류 없이 평문 볼륨을 마운트하게 됩니다.

악용 (스토리지 쓰기 접근 필요)

Preconditions:

  • CVM의 LUKS2로 암호화된 블록 디바이스에 대한 쓰기 접근.
  • guest가 온디스크 LUKS2 헤더를 강력한 검증/attestation 없이 사용함.

Steps (high level):

  1. 헤더 JSON을 읽고 data segment 정의를 식별합니다. 예시 대상 필드: segments[“0”].encryption.
  2. 데이터 세그먼트의 암호화를 null cipher로 설정합니다(예: cipher_null-ecb). guest의 일반적인 패스프레이즈가 여전히 “작동“하도록 keyslot 파라미터와 digest 구조는 그대로 유지합니다.
  3. 두 헤더 복사본과 연관된 header digests를 업데이트하여 헤더가 자체적으로 일관되게 만듭니다.
  4. 다음 부팅 시 guest는 cryptsetup을 실행하고 기존 keyslot을 패스프레이즈로 성공적으로 언락한 뒤 볼륨을 마운트합니다. 세그먼트 cipher가 null cipher이므로 모든 읽기/쓰기 동작은 평문입니다.

Variant (pre-2.8.1 keyslot abuse): keyslot의 area.encryption이 null cipher라면 어떤 패스프레이즈로도 열립니다. 이를 null segment cipher와 결합하면 guest의 비밀을 알지 못해도 원활하게 평문에 접근할 수 있습니다.

강력한 완화책 (detached headers로 TOCTOU 회피)

온디스크 LUKS 헤더는 항상 신뢰할 수 없는 입력으로 취급하십시오. detached-header 모드를 사용해 검증과 오픈이 보호된 RAM의 동일한 신뢰된 바이트를 사용하도록 하십시오:

# 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

그런 다음 다음 중 하나(또는 그 이상)를 적용하세요:

  1. 전체 헤더에 MAC 적용
  • 사용하기 전에 전체 헤더에 대해 MAC을 계산/검증하세요.
  • MAC이 검증될 때만 볼륨을 엽니다.
  • 실제 사례: Flashbots tdx-init과 Fortanix Salmiac이 MAC 기반 검증을 채택했습니다.
  1. 엄격한 JSON 검증(하위 호환성 유지)
  • JSON 메타데이터를 덤프하고 파라미터의 엄격한 허용 목록을 검증합니다 (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
예시 validator (안전한 필드 강제) ```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. 헤더 측정/증명
  • 무작위 salts/digests를 제거하고 정제된 헤더를 TPM/TDX/SEV PCRs 또는 KMS policy state에 측정(measure)하십시오.
  • 측정된 헤더가 승인된 안전 프로파일과 일치할 때만 복호화 키를 해제하십시오.

운영 지침:

  • detached header + MAC을 강제하거나 엄격한 검증을 적용하십시오; 디스크 상의(on-disk) 헤더를 직접 신뢰하지 마십시오.
  • attestation의 소비자는 허용 목록에서 패치 이전(pre-patch) 프레임워크 버전을 거부해야 합니다.

버전 및 유지관리자 입장

  • cryptsetup maintainers는 LUKS2가 이 환경에서 스토리지 변조에 대한 무결성을 제공하도록 설계되지 않았음을 명확히 했으며; null ciphers는 하위 호환성을 위해 유지됩니다.
  • cryptsetup 2.8.1 (Oct 19, 2025)은 비어있지 않은 비밀번호가 설정된 keyslots에 대해 null ciphers를 거부하지만 여전히 segments에 대해서는 null ciphers를 허용합니다.

빠른 점검 및 분류

  • 세그먼트(segment) 암호화가 null cipher로 설정되어 있는지 확인:
cryptsetup luksDump --type luks2 --dump-json-metadata /dev/VDISK \
| jq -r '.segments | to_entries[] | "segment=" + .key + ", enc=" + .value.encryption'
  • 볼륨을 열기 전에 keyslot 및 segment 알고리즘을 검증하세요. MAC을 적용할 수 없다면 엄격한 JSON 유효성 검사를 강제하고 보호된 메모리에서 분리된 헤더(detached header)를 사용해 여세요.

References

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기