SBOM 공급망 취약점 분석은(는) 무엇을 하나요?
사용 시점
- 새로운 규제 요구사항(EO 14028, EU CRA)이 소프트웨어 납품에 SBOM 분석을 의무화하는 경우
- 보안 팀이 벤더가 제공한 SBOM을 스캔하여 서드파티 리스크를 평가해야 하는 경우
- CI/CD 파이프라인에서 생성된 SBOM에 대해 자동화된 취약점 점검이 필요한 경우
- 침해 대응 시 새로 공개된 CVE가 배포된 소프트웨어에 영향을 미치는지 확인해야 하는 경우
- 조달 팀이 소프트웨어 구매를 위한 공급망 리스크 평가를 요청하는 경우
사용하지 말아야 할 경우: 라이브 시스템의 런타임 취약점 스캔. 대신 컨테이너 스캐닝 도구(Trivy, Grype CLI) 또는 호스트 기반 취약점 스캐너(Nessus, Qualys)를 사용하십시오.
사전 요구사항
- CycloneDX JSON(v1.4+) 또는 SPDX JSON(v2.3+) 형식의 SBOM 파일
- Python 3.9+ 및 requests, networkx, packaging 라이브러리 설치
- 더 높은 rate limit을 위한 NVD API 키(무료, https://nvd.nist.gov/developers/request-an-api-key)
- NVD API 네트워크 접근 (https://services.nvd.nist.gov/rest/json/cves/2.0)
- 선택 사항: SBOM 생성을 위한 syft, 교차 검증을 위한 grype
워크플로우
1단계: SBOM 생성 (제공되지 않은 경우)
syft를 사용하여 컨테이너 이미지 또는 프로젝트 디렉터리에서 SBOM을 생성합니다:
# 컨테이너 이미지에서 CycloneDX JSON 생성
syft alpine:latest -o cyclonedx-json > sbom-cyclonedx.json
# 프로젝트 디렉터리에서 SPDX JSON 생성
syft dir:/path/to/project -o spdx-json > sbom-spdx.json
# 실행 중인 컨테이너에서 생성
syft docker:my-app-container -o cyclonedx-json > sbom.json
Syft는 npm, PyPI, Maven, Go 모듈, apt, apk, RPM을 포함한 30개 이상의 패키지 생태계를 지원합니다. 생성된 SBOM에는 패키지 이름, 버전, 라이선스, CPE 식별자 및 PURL(Package URL) 참조가 포함됩니다.
2단계: SBOM 파싱 및 구성 요소 추출
SBOM을 파싱하여 식별자와 함께 모든 소프트웨어 구성 요소를 추출합니다:
CycloneDX JSON 구조:
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"components": [
{
"type": "library",
"name": "lodash",
"version": "4.17.20",
"purl": "pkg:npm/[email protected]",
"cpe": "cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:*:*:*",
"licenses": [{"license": {"id": "MIT"}}]
}
],
"dependencies": [
{"ref": "pkg:npm/[email protected]", "dependsOn": ["pkg:npm/[email protected]"]}
]
}
SPDX JSON 구조:
{
"spdxVersion": "SPDX-2.3",
"packages": [
{
"name": "lodash",
"versionInfo": "4.17.20",
"externalRefs": [
{"referenceType": "purl", "referenceLocator": "pkg:npm/[email protected]"},
{"referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:*:*:*"}
],
"licenseConcluded": "MIT"
}
],
"relationships": [
{"spdxElementId": "SPDXRef-express", "relatedSpdxElement": "SPDXRef-lodash",
"relationshipType": "DEPENDS_ON"}
]
}
3단계: 구성 요소를 NVD CVE 데이터베이스와 연계
NVD 2.0 API를 쿼리하여 각 구성 요소에 대한 알려진 취약점을 찾습니다:
import requests
NVD_API = "https://services.nvd.nist.gov/rest/json/cves/2.0"
def search_cves_by_cpe(cpe_name, api_key=None):
params = {"cpeName": cpe_name, "resultsPerPage": 50}
headers = {"apiKey": api_key} if api_key else {}
resp = requests.get(NVD_API, params=params, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json().get("vulnerabilities", [])
def search_cves_by_keyword(keyword, version=None, api_key=None):
params = {"keywordSearch": keyword, "resultsPerPage": 50}
headers = {"apiKey": api_key} if api_key else {}
resp = requests.get(NVD_API, params=params, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json().get("vulnerabilities", [])
NVD API는 CPE 이름(가장 정확함), 키워드, CVE ID, 날짜 범위로 검색을 지원합니다. Rate limit: API 키 없이 30초당 5회 요청, 키 있으면 30초당 50회 요청.
4단계: 의존성 그래프 구축 및 전이적 위험 식별
취약점 전파를 추적하기 위해 방향성 그래프를 구축합니다:
import networkx as nx
def build_dependency_graph(sbom):
G = nx.DiGraph()
# 각 구성 요소에 대한 노드 추가
for comp in sbom["components"]:
G.add_node(comp["purl"], name=comp["name"], version=comp["version"])
# 의존성 관계에서 엣지 추가
for dep in sbom.get("dependencies", []):
for child in dep.get("dependsOn", []):
G.add_edge(dep["ref"], child)
return G
전이적 의존성 분석은 직접 포함되지 않았지만 의존성 체인을 통해 가져온 구성 요소를 식별합니다. 깊게 중첩된 전이적 의존성(예: 4단계 깊이)의 취약점도 위험을 나타내지만 수정이 더 어려울 수 있습니다.
리스크 평가를 위한 핵심 그래프 지표:
- 진입 차수(In-degree): 이 구성 요소에 의존하는 구성 요소 수 (진입 차수가 높음 = 폭발 반경이 큼)
- 루트까지의 최단 경로: 애플리케이션 진입점으로부터의 거리 (가까울수록 악용 가능성 높음)
- 중개 중심성(Betweenness centrality): 많은 의존성 경로에 위치한 구성 요소 (병목 위험)
5단계: 위험 점수 계산
취약점 데이터를 구성 요소 및 전체 위험 점수로 집계합니다:
위험 점수 계산:
━━━━━━━━━━━━━━━━━━━━━━
구성 요소 위험 = 해당 구성 요소에 영향을 미치는 모든 CVE의 CVSS 점수 중 최대값
가중 위험 = 구성 요소 위험 * 의존성 계수
여기서 의존성 계수 = 1.0 + (0.1 * 진입 차수)
(의존자가 많을수록 조직 영향도 증가)
전체 SBOM 위험 = 모든 구성 요소 위험의 가중 평균
의존성 중개 중심성으로 가중치 부여
위험 수준:
CRITICAL: CVSS >= 9.0 또는 알려진 악용(CISA KEV)
HIGH: CVSS >= 7.0
MEDIUM: CVSS >= 4.0
LOW: CVSS < 4.0
6단계: Grype로 교차 검증
grype를 사용하여 SBOM을 독립적으로 스캔하고 결과를 비교합니다:
# CycloneDX SBOM을 grype로 스캔
grype sbom:sbom-cyclonedx.json -o json > grype-results.json
# SPDX SBOM 스캔
grype sbom:sbom-spdx.json -o table
# 심각도별 필터링
grype sbom:sbom-cyclonedx.json --only-fixed --fail-on critical
Grype는 NVD, GitHub Security Advisories, Alpine SecDB, Red Hat, Debian, Ubuntu, Amazon Linux, Oracle 보안 데이터베이스에서 취약점 데이터를 가져와 NVD 단독보다 더 넓은 커버리지를 제공합니다.
7단계: 규정 준수 보고서 생성
규제 준수에 적합한 구조화된 보고서를 생성합니다:
SBOM 취약점 분석 보고서
====================================
SBOM 파일: app-sbom-cyclonedx.json
형식: CycloneDX v1.5
분석 일자: 2026-03-19
총 구성 요소 수: 247
총 의존성 수: 1,842 (직접: 34, 전이: 213)
취약점 요약
Critical: 3개 구성 요소 / 5개 CVE
High: 11개 구성 요소 / 18개 CVE
Medium: 27개 구성 요소 / 41개 CVE
Low: 8개 구성 요소 / 12개 CVE
CRITICAL 발견 사항
1. [email protected]
CVE-2021-23337 (CVSS 7.2) - 템플릿을 통한 명령 주입
CVE-2020-28500 (CVSS 5.3) - trimEnd의 ReDoS
의존자: 14개 구성 요소 (폭발 반경 큼)
수정: 4.17.21+으로 업그레이드
2. [email protected]
CVE-2021-44228 (CVSS 10.0) - Log4Shell RCE [CISA KEV]
CVE-2021-45046 (CVSS 9.0) - 불완전한 수정 우회
의존자: 8개 구성 요소
수정: 2.17.1+으로 업그레이드
의존성 그래프 위험
가장 많이 의존된 구성 요소: [email protected] (의존자 47개)
가장 깊은 체인: app -> framework -> adapter -> codec -> zlib (5단계)
병목 구성 요소: 의존성 경로의 50% 이상에 위치한 3개 구성 요소
라이선스 컴플라이언스
발견된 카피레프트 라이선스: 2개 (libxml2의 GPL-3.0, mongodb-driver의 AGPL-3.0)
상업적 배포 전 검토 필요
주요 개념
| 용어 | 정의 |
|---|---|
| SBOM | Software Bill of Materials; 소프트웨어 제품의 모든 구성 요소, 라이브러리 및 의존성에 대한 공식 인벤토리 |
| CycloneDX | OWASP 유지 관리 SBOM 표준. 의존성 그래프 및 취약점 데이터와 함께 JSON, XML, protobuf 형식 지원 |
| SPDX | Linux Foundation SBOM 표준. 패키지, 파일, 스니펫 수준의 세부 정보와 함께 라이선스 컴플라이언스에 초점 |
| PURL | Package URL; 생태계 간 소프트웨어 패키지를 식별하기 위한 표준화된 체계 (예: pkg:npm/[email protected]) |
| CPE | Common Platform Enumeration; IT 제품에 대한 NIST 명명 체계로, NVD CVE 데이터와 연계하는 데 사용 |
| NVD | National Vulnerability Database; CVE 식별자로 색인된 취약점 데이터의 미국 정부 저장소 |
| 전이적 의존성 | 직접 선언되지 않았지만 직접 의존성의 의존성 체인을 통해 가져온 의존성 |
| CISA KEV | CISA 알려진 악용 취약점 카탈로그; 실제로 활발히 악용되고 있음이 확인된 CVE |
도구 및 시스템
- syft (Anchore): 오픈 소스 SBOM 생성기. 30개 이상의 패키지 생태계와 CycloneDX/SPDX 출력 지원
- grype (Anchore): SBOM을 입력으로 받아 여러 자문 데이터베이스와 연계하는 취약점 스캐너
- cyclonedx-python-lib: CycloneDX SBOM을 프로그래밍 방식으로 생성, 파싱 및 검증하는 Python 라이브러리
- lib4sbom: SPDX 및 CycloneDX 형식 SBOM을 모두 파싱하는 Python 라이브러리
- nvdlib: rate limit 관리를 통해 CVE 및 CPE 쿼리를 지원하는 NVD 2.0 API용 Python 래퍼
- OWASP Dependency-Track: 지속적인 SBOM 분석, 취약점 추적 및 정책 시행을 위한 플랫폼
일반적인 시나리오
시나리오: Log4Shell 공개 후 벤더 소프트웨어 평가
컨텍스트: Log4Shell(CVE-2021-44228) 공개 이후, 보안 팀은 취약한 log4j 버전을 포함하는 벤더 제공 애플리케이션을 식별해야 합니다. 여러 벤더가 계약 요구사항에 따라 SBOM을 제공했습니다.
접근 방식:
- 모든 벤더 SBOM 수집(CycloneDX 또는 SPDX JSON 형식)
- 각 SBOM을 파싱하고 버전 < 2.17.1인 log4j-core 구성 요소 검색
- NVD API에서 특정 CVE(CVE-2021-44228, CVE-2021-45046, CVE-2021-45105) 쿼리
- 의존성 그래프를 구축하여 log4j에 의존하는 애플리케이션 구성 요소 식별
- 폭발 반경 계산: 노출된 서비스 및 엔드포인트 수
- 노출도 및 비즈니스 중요도별로 정렬된 우선 순위 수정 보고서 생성
- 동일한 SBOM에 대한 grype 스캔 결과와 교차 검증
주의사항:
- 벤더 SBOM이 불완전하여 log4j를 포함하는 섀도우/번들 JAR 파일이 누락될 수 있음
- SPDX와 CycloneDX 버전 차이가 파서 호환성에 영향을 줄 수 있음
- NVD API rate limit은 API 키 없이 수백 개 구성 요소를 스캔할 때 분석 속도를 저하시킬 수 있음
- SBOM의 CPE 이름이 NVD 항목과 정확히 일치하지 않아 퍼지 매칭이 필요할 수 있음
- 전이적 의존성은 log4j가 직접 의존성이 아닌 경우에도 포함될 수 있음