Docker Hardened Images에서 VEX 문 탐구하기

Docker Hardened Images에서 VEX 문 탐구하기

VEX가 있거나 없는 상태로 Docker Hardened Image를 스캔하고, 모든 억제와 그 근거를 감사해볼게요.

출처: 문서

본문

표준 취약점 스캐너는 이미지에 존재하는 패키지에 대해 CVE를 보고해요. Docker Hardened Images에서 그 패키지들은 설계상 존재하지만, 보고된 각 CVE에는 이 특정 제품 구성에서 악용 가능한지 여부를 설명하는 VEX 문이 있어요. 이 가이드는 VEX가 있거나 없는 상태로 Docker Hardened Image를 스캔하고, 모든 억제에 깔린 근거를 감사하는 과정을 안내해요.

사전 요구사항

  • Docker Desktop 이 dhi.io 에 인증돼 있어야 해요. docker login dhi.io 로 로그인하세요. Docker Desktop에는 VEX 증명을 가져오는 Docker Scout가 포함돼 있어요.
  • 취약점 스캐너. 이 가이드는 Docker Scout, Trivy , Grype 의 예제를 보여줘요. Trivy와 Grype는 설치 없이 Docker 컨테이너로도 실행할 수 있어요.
  • jq (선택), VEX 파일 필터링용.

Windows 사용자용 데몬 노출

이 가이드 전체에서 컨테이너화된 스캐너 명령에 사용되는 -v /var/run/docker.sock:/var/run/docker.sock 소켓 마운트는 Windows용 Docker Desktop에서 작동하지 않아요. Windows에서 컨테이너화된 스캐너를 사용하려면 Docker Desktop의 Settings > General 로 가서 Expose daemon on tcp://localhost:2375 without TLS 를 켜주세요. 그런 다음 모든 컨테이너화된 스캐너 명령에서 -v /var/run/docker.sock:/var/run/docker.sock 을 -e DOCKER_HOST=tcp://host.docker.internal:2375 로 바꿔주세요.

경고: TLS 없이 데몬을 TCP로 노출하면 시스템이 원격 코드 실행 공격에 취약해져요. 테스트를 마치면 설정을 꺼주세요.

1단계: VEX 없이 스캔하기

Docker Hardened Images 레지스트리에 로그인해주세요:

$ docker login dhi.io

그다음 이미지를 내려받아주세요:

$ docker pull dhi.io/python:3.13

그다음 VEX 없이 스캔해 원시 CVE 수를 확인해주세요. Docker Scout는 Docker Hardened Images에 자동으로 VEX를 적용해요. 필터링되지 않은 CVE 기준선을 보려면 Trivy나 Grype를 사용하세요.

Trivy

$ trivy image --scanners vuln dhi.io/python:3.13

Trivy가 설치되어 있지 않다면 컨테이너에서 실행하세요:

$ docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy:latest image --scanners vuln dhi.io/python:3.13

출력 예시:

Total: 30 (UNKNOWN: 0, LOW: 15, MEDIUM: 11, HIGH: 4, CRITICAL: 0)

Grype

$ grype dhi.io/python:3.13

Grype가 설치되어 있지 않다면 컨테이너에서 실행하세요:

$ docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  anchore/grype:latest docker:dhi.io/python:3.13

출력 예시:

NAME   INSTALLED      FIXED IN       TYPE   VULNERABILITY  SEVERITY
libc6  2.41-12+deb13u2                deb    CVE-2018-20796 Negligible
libc6  2.41-12+deb13u2  (won't fix)   deb    CVE-2026-4437  High
libc6  2.41-12+deb13u2  (won't fix)   deb    CVE-2026-5450  Critical
...

출력은 libc6 , libncursesw6 , libsqlite3-0 , libuuid1 , zlib1g 등 Python이 동작하는 데 필요한 모든 런타임 의존성에 걸친 CVE를 나열해요. 이 패키지들은 설계상 존재해요.

이런 스캔 결과가 보고된 모든 CVE에 패치가 필요하다는 뜻은 아니에요. 이는 이 이미지에 존재하는 패키지에 대해 CVE가 보고됐다는 뜻이에요. 그 CVE 중 어떤 것이 이 구성에서 실제로 악용 가능한지는 별개의 질문이며, 바로 그 질문에 VEX가 답해요.

2단계: VEX 증명 가져오기

VEX 증명을 로컬 파일로 내보내주세요:

$ docker scout vex get registry://dhi.io/python:3.13 --output python-vex.json

registry:// 접두사는 Scout가 로컬 이미지 저장소 대신 레지스트리에서 증명을 가져오도록 지시해요. 1단계에서 이미지를 내려받았으므로 이미 로컬에 존재하고, 이 접두사가 없으면 Scout는 거기서 증명을 찾지 못해요.

이것은 Docker의 모든 Docker Hardened Images용 공급망 메타데이터 레지스트리인 registry.scout.docker.com 에서 서명된 OpenVEX 문서를 가져와요. 이 문서는 이미지의 SBOM에서 발견된 모든 CVE에 대한 Docker의 악용 가능성 평가를 기록해요.

참고: Docker Scout는 스캔할 때 이 파일을 자동으로 가져와요. 네이티브로 통합하지 않는 스캐너용으로 또는 5단계·6단계에서 jq 쿼리를 실행하려면 명시적으로만 다운로드하면 돼요.

3단계: VEX를 적용해 스캔하기

Docker Scout

Docker Scout는 로컬 파일 없이 VEX 증명을 자동으로 가져오고 적용해요:

$ docker scout cves dhi.io/python:3.13

출력 예시:

✓ SBOM obtained from attestation, 47 packages indexed
✓ Provenance obtained from attestation
✓ VEX statements obtained from attestation
✓ No vulnerable package detected

Trivy

--vex 플래그로 VEX 파일을 전달해주세요:

$ trivy image --scanners vuln --vex python-vex.json dhi.io/python:3.13

Trivy가 설치되어 있지 않다면 컨테이너에서 실행하세요:

$ docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$(pwd)/python-vex.json:/tmp/vex.json" \
  aquasec/trivy:latest image --scanners vuln --vex /tmp/vex.json dhi.io/python:3.13

출력 예시:

Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
Some vulnerabilities have been ignored/suppressed. Use the '--show-suppressed' flag to display them.

Grype

--vex 플래그로 VEX 파일을 전달해주세요:

$ grype dhi.io/python:3.13 --vex python-vex.json

Grype가 설치되어 있지 않다면 컨테이너에서 실행하세요:

$ docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$(pwd)/python-vex.json:/tmp/vex.json" \
  anchore/grype:latest docker:dhi.io/python:3.13 --vex /tmp/vex.json

출력 예시:

No vulnerabilities found

같은 이미지, 같은 패키지, 같은 CVE 데이터베이스예요. 유일한 차이는 컨텍스트예요. 스캐너가 각 CVE를 VEX 파일과 대조하고 Docker가 악용 불가라고 평가한 모든 항목을 억제했어요.

패키지는 여전히 존재해요. SBOM을 확인하면 1단계의 libc6 , libsqlite3-0 등 모든 패키지가 보일 거예요. CVE 0개가 패키지가 제거됐다는 것을 의미하지는 않아요. 각 보고된 CVE가 이 제품 구성에 적용되지 않는 이유에 대한 문서화된 근거가 있다는 뜻이에요.

VEX는 공개 표준이에요: 증명은 이미지와 함께 이동하며, 규정을 따르는 어떤 스캐너든 같은 근거를 읽어요.

4단계: 모든 억제와 근거 검사하기

Docker Scout와 Grype는 VEX와 일치하는 CVE를 억제하지만 출력에서 근거 코드는 표면화하지 않아요. Trivy의 --show-suppressed 플래그를 사용해 모든 억제된 CVE를 CVE별 근거 코드와 함께 보세요.

$ trivy image --scanners vuln --vex python-vex.json --show-suppressed dhi.io/python:3.13

Trivy가 설치되어 있지 않다면 컨테이너에서 실행하세요:

$ docker run --rm \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v "$(pwd)/python-vex.json:/tmp/vex.json" \
  aquasec/trivy:latest image --scanners vuln --vex /tmp/vex.json --show-suppressed dhi.io/python:3.13

출력 예시:

Suppressed Vulnerabilities (Total: 28)
======================================
┌──────────────┬──────────────────┬──────────┬──────────────┬───────────────────────────────────────────────────┐
│ Library      │ Vulnerability    │ Severity │ Status       │ Statement                                          │
├──────────────┼──────────────────┼──────────┼──────────────┼───────────────────────────────────────────────────┤
│ libc6        │ CVE-2010-4756    │ LOW      │ not_affected │ vulnerable_code_cannot_be_controlled_by_adversary │
│ libsqlite3-0 │ CVE-2025-70873   │ LOW      │ not_affected │ vulnerable_code_not_present                       │
│ ...          │ ...              │ ...      │ ...          │ ...                                               │
└──────────────┴──────────────────┴──────────┴──────────────┴───────────────────────────────────────────────────┘

Statement 열은 VEX 파일의 기계 판독 가능한 근거 코드를 보여줘요. 근거 코드는 정확한 의미를 가져요:

  • vulnerable_code_cannot_be_controlled_by_adversary : 취약한 코드 경로가 패키지에 존재하지만, 이 구성에서 공격자가 이를 트리거할 수 없어요.
  • vulnerable_code_not_present : 취약한 코드가 이 빌드에 컴파일되지 않았거나 다른 이유로 존재하지 않아요.
  • inline_mitigations_already_exist : Docker가 이 이미지에서 CVE를 해결하는 백포트 또는 패치를 적용했어요.

근거 코드의 전체 목록은 VEX 상태 참조 를 참고하세요.

모든 억제는 문서화되어 있고 감사 가능하며 VEX 지원 스캐너로 검증할 수 있어요.

5단계: 특정 CVE에 대한 Docker의 근거 읽기

근거 코드는 기계 판독 가능해요. VEX 파일의 status_notes 필드에는 Docker의 사람이 읽을 수 있는 근거가 들어 있어요. jq 를 사용해 특정 CVE를 조회해주세요:

$ jq '.statements[] | select(.vulnerability.name == "CVE-2010-4756") | {status, justification, status_notes}' python-vex.json

출력 예시:

{
  "status": "not_affected",
  "justification": "vulnerable_code_cannot_be_controlled_by_adversary",
  "status_notes": "Standard POSIX behavior in glibc. Applications using glob need to impose limits themselves. Requires authenticated access and is considered unimportant by Debian."
}

status_notes 필드는 Docker의 근거를 평이한 언어로 설명해요. CVE-2010-4756의 경우 CVE가 설명하는 glob 동작은 표준 POSIX 동작이고 인증된 접근이 필요하며 Debian 보안 팀이 중요하지 않은 것으로 분류해요.

각 문은 또한 영향받는 제품을 PURL(Package URL)로 나열해요. 예: pkg:deb/debian/[email protected]%2Bdeb13u2?os_distro=trixie&os_name=debian&os_version=13 .

Trivy는 그 PURL을 SBOM에 기록된 패키지와 비교해 이 문을 이미지의 libc6 에 매칭했어요.

중요: PURL 매칭은 엄격해요. 스캐너는 os_name , os_version , os_distro 한정자를 포함한 전체 PURL 문자열로 VEX 문을 패키지에 매칭해야 해요. 패키지 이름만으로 매칭하면 한 OS 버전의 억제를 CVE가 악용 가능한 다른 버전에 적용할 위험이 있어요.

6단계: 상태별로 VEX 문 필터링하기

python-vex.json 이 생기면 jq 로 직접 쿼리해주세요.

상태별로 문을 세어보세요:

$ jq '[.statements[].status] | group_by(.) | map({status: .[0], count: length})' python-vex.json

활발히 조사 중인 모든 CVE를 나열하세요:

$ jq '[.statements[] | select(.status == "under_investigation") | {cve: .vulnerability.name, products: [.products[].\"@id\"]}]' python-vex.json

affected 상태인 CVE를 나열하세요:

$ jq '[.statements[] | select(.status == "affected") | {cve: .vulnerability.name, action: .action_statement}]' python-vex.json

affected 쿼리는 현재 dhi.io/python:3.13 이미지에 대해 빈 배열을 반환하는데, 이는 활발히 유지되는 태그에 대한 예상된 결과예요. 모든 DHI Python 버전의 affected 항목을 보려면 전체 VEX 피드를 쿼리하세요:

$ curl -s https://raw.githubusercontent.com/docker-hardened-images/advisories/main/vex/python/dhi-python.vex.json \
  | jq '[.statements[] | select(.status == "affected") | {cve: .vulnerability.name, action: .action_statement}]'

상태 정의와 근거 코드는 VEX 상태 참조 를 참고하세요.

다음 단계

  • 다른 도구로 스캔하기: Trivy(VEX Hub)와 Grype로 DHI VEX 문을 적용하는 방법을 Docker Hardened Images 스캔하기 에서 배워보세요.
  • 하위 이미지용 나만의 VEX 작성하기: DHI 위에 빌드하면서 추가하는 패키지의 CVE를 억제하고 싶다면 VEX로 예외 만들기 를 참고하세요.
  • VEX 상태 참조: 상태 정의, 근거 코드, DHI가 fixed 를 사용하지 않는 이유는 Vulnerability Exploitability eXchange(VEX) 를 참고하세요.
  • VEX 피드 직접 보기: 원시 VEX 데이터는 github.com/docker-hardened-images/advisories 에 이미지 이름별로 게시돼 있어요.

더 알아보기 (Learn more)

  • Docker Hardened Images
  • VEX 상태 참조
  • Docker Scout