Docker 이미지 취약점 스캔: Trivy와 Docker Scout

Trivy, Docker Scout, Grype를 이용한 컨테이너 이미지 CVE 스캔, CI/CD 파이프라인 통합, SBOM 생성, 취약점 우선순위 결정 전략을 실전 예제와 함께 설명합니다.

· 7 min read · PALDYN Team

지난 글에서 이미지 서명으로 변조를 방지하는 방법을 다뤘다. 이번에는 이미지 **내부의 취약점(CVE)**을 찾아내는 스캔 방법을 살펴본다. 서명이 “이 이미지가 내가 만든 것임”을 보장한다면, 취약점 스캔은 “이 이미지가 안전한가”를 검사한다.

왜 이미지를 스캔해야 하는가

컨테이너 이미지는 수십~수백 개의 패키지를 포함한다. 베이스 이미지(예: node:20-alpine)만 해도 이미 다수의 패키지가 들어있고, 그 중 일부는 알려진 CVE를 가진다.

# 이미지 안에 포함된 패키지 예시
docker run --rm node:20-alpine sh -c "apk info | wc -l"
# → 수십 개의 Alpine 패키지

# 그 중 취약한 패키지가 있을 수 있다

취약점 스캔을 CI에 통합해 새로운 CVE가 프로덕션에 배포되기 전에 차단하는 것이 목표다.

Trivy

Aqua Security의 오픈소스 스캐너로 가장 널리 쓰인다.

# 설치 (Homebrew)
brew install aquasecurity/trivy/trivy

# Linux (스크립트)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# 기본 스캔
trivy image node:20-alpine

# 심각도 필터 (HIGH 이상만)
trivy image --severity HIGH,CRITICAL node:20-alpine

# JSON 출력
trivy image --format json --output result.json node:20-alpine

# 로컬 빌드된 이미지 스캔
trivy image myapp:latest

Trivy 출력 해석

node:20-alpine (alpine 3.19.0)
Total: 3 (HIGH: 1, MEDIUM: 2)

┌──────────────┬───────────────┬──────────┬──────────────────┐
│   Library    │ Vulnerability │ Severity │ Fixed Version    │
├──────────────┼───────────────┼──────────┼──────────────────┤
│ openssl      │ CVE-2024-XXXX │ HIGH     │ 3.1.5-r0         │
│ libcrypto3   │ CVE-2024-YYYY │ MEDIUM   │ 3.1.5-r0         │
└──────────────┴───────────────┴──────────┴──────────────────┘

Fixed Version이 있으면 베이스 이미지를 최신 버전으로 업데이트하거나 해당 패키지만 업그레이드해 해결할 수 있다.

Dockerfile에서 패키지 업그레이드

FROM node:20-alpine
# 알려진 취약한 패키지만 선택 업그레이드
RUN apk upgrade --no-cache openssl libcrypto3
# 전체 업그레이드 (트레이드오프: 재현성 감소)
# RUN apk upgrade --no-cache

Docker Scout

Docker 공식 취약점 스캐너로 Docker Desktop과 CLI에 통합되어 있다.

# Docker Scout 활성화 (로그인 필요)
docker login

# 빠른 요약
docker scout quickview myimage:latest

# CVE 전체 목록
docker scout cves myimage:latest

# 심각도 필터
docker scout cves --only-severity critical,high myimage:latest

# 베이스 이미지 비교 (어떤 이미지로 바꾸면 CVE가 줄어드는지)
docker scout recommendations myimage:latest

Scout의 recommendations 명령은 더 안전한 베이스 이미지를 자동으로 추천해준다.

# 출력 예시
# Base image recommendation:
#   node:20-alpine → node:20.11-alpine3.19
#   (reduces CVE count from 15 to 3)

이미지 취약점 스캔 도구 비교

SBOM(Software Bill of Materials)

SBOM은 이미지에 포함된 모든 소프트웨어 구성 요소 목록이다. 새 CVE가 발표됐을 때 어떤 이미지가 영향받는지 빠르게 파악할 수 있다.

# Trivy로 SBOM 생성 (CycloneDX 형식)
trivy image --format cyclonedx --output sbom.json myapp:latest

# Syft(Anchore)로 SBOM 생성
syft myapp:latest -o cyclonedx-json > sbom.json

# Grype로 SBOM 기반 스캔 (이미지 없이 SBOM만으로 스캔 가능)
grype sbom:sbom.json

CI/CD 통합

취약점 스캔은 이미지 빌드 직후, 레지스트리 push 전에 실행하는 것이 이상적이다.

CI 파이프라인에서 이미지 스캔

GitHub Actions (Trivy)

- name: Trivy vulnerability scan
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:${{ github.sha }}
    severity: 'CRITICAL,HIGH'
    exit-code: '1'        # 취약점 발견 시 빌드 실패
    format: 'sarif'
    output: 'trivy-results.sarif'

- name: Upload SARIF to GitHub Security
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: trivy-results.sarif

SARIF 형식으로 업로드하면 GitHub Security 탭에서 결과를 시각적으로 확인할 수 있다.

GitLab CI

trivy-scan:
  image: aquasec/trivy:latest
  script:
    - trivy image
        --exit-code 1
        --severity CRITICAL,HIGH
        --no-progress
        $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  allow_failure: false

로컬 pre-push 훅

#!/bin/sh
# .git/hooks/pre-push
IMAGE=$(grep 'image:' Dockerfile | head -1 | awk '{print $2}')
trivy image --exit-code 1 --severity CRITICAL $IMAGE

취약점 우선순위 결정

스캔 결과에 수백 개의 CVE가 나왔다고 해서 모두 즉시 고쳐야 하는 것은 아니다.

# 실제로 수정 가능한 취약점만 보기 (fix-availability 필터)
trivy image --ignore-unfixed myapp:latest

# CVSSv3 점수 8.0 이상만
trivy image --severity CRITICAL myapp:latest

우선순위 기준:

  1. Fix Available + CRITICAL/HIGH — 즉시 패치
  2. No Fix + CRITICAL — 베이스 이미지 교체 또는 distroless/scratch 고려
  3. MEDIUM with Fix — 다음 릴리즈에 포함
  4. LOW/NEGLIGIBLE — 무시 또는 .trivyignore에 등록

.trivyignore로 오탐 제외

# .trivyignore
# 오탐 또는 영향 없는 CVE 제외 (재현 불가하거나 non-exploitable)
CVE-2023-XXXXX
CVE-2024-YYYYY

런타임 스캔

이미지 빌드 시점뿐만 아니라 이미 실행 중인 컨테이너도 스캔할 수 있다.

# 실행 중인 컨테이너 스캔
trivy image --input <(docker export mycontainer)

# 또는 컨테이너 ID로
trivy image $(docker inspect --format='{{.Image}}' mycontainer)

스캔 도구 선택 가이드

  • 개인 프로젝트 / 오픈소스: Trivy (무료, 기능 풍부)
  • Docker Hub 중심 워크플로우: Docker Scout (CLI 통합 편리)
  • Anchore 엔터프라이즈 연동: Grype + Syft
  • Kubernetes 환경: Trivy Operator (클러스터 내 지속 스캔)

지난 글: Docker Content Trust: 이미지 서명으로 공급망 공격 방지

다음 글: Docker 읽기 전용 루트 파일 시스템: —read-only 완전 활용


읽어주셔서 감사합니다. 😊