Mục tiêu học tập
- Hiểu SBOM (Software Bill of Materials) là gì, vì sao đang trở thành yêu cầu compliance
- So sánh hai chuẩn SBOM: SPDX và CycloneDX
- Biết các công cụ sinh SBOM: syft, cdxgen,
npm sbom, Trivy - Nắm pipeline dependency scanning: npm/pip-audit, Snyk, Dependabot, Renovate
- Biết container image scanning: Trivy, Grype, Anchore
- Hiểu CVE database, CVSS score, và khác biệt giữa severity và exploitability
- Tích hợp scanning vào CI: fail build trên high-severity CVE, allowlist khi cần
1. SBOM là gì?
SBOM = "Software Bill of Materials"
Tương tự như:
- BOM (Bill of Materials) trong sản xuất ô-tô: danh sách linh kiện
- Nutrition label trên thực phẩm: liệt kê thành phần
SBOM cho phần mềm: liệt kê MỌI component (lib, framework, tool) trong
một artifact (binary, container image, source release), kèm:
- Tên component
- Version
- License
- Supplier
- Hash (cryptographic checksum)
- Quan hệ (cái nào depends on cái nào)
Vì sao SBOM trở thành bắt buộc
2020: SolarWinds attack — backdoor trong Orion software ảnh hưởng 18,000 tổ chức
2021: Log4Shell (CVE-2021-44228) — log4j 2.x có RCE, mọi app Java dùng log4j bị
Câu hỏi đau: "tôi có dùng log4j không? phiên bản nào? ở đâu?"
Nếu không có SBOM → phải scan thủ công hàng trăm app trong vài giờ
2021-05: Executive Order 14028 (US) — yêu cầu SBOM cho mọi software bán cho US gov
2023-Q4: EU Cyber Resilience Act (CRA) — yêu cầu SBOM cho hardware/software bán EU
2024: SBOM bắt đầu trở thành mandatory với critical infra
Use case cụ thể
- Incident response: CVE mới công bố → query SBOM database → biết ngay app nào bị ảnh hưởng
- License compliance: đảm bảo không vô tình ship code GPL trong sản phẩm proprietary
- Supply chain audit: customer hỏi "bạn có dùng dep X không?" → trả lời chính xác
- Vulnerability management: continuous scan SBOM với CVE database
2. SPDX vs CycloneDX
SPDX (Software Package Data Exchange)
- ISO/IEC 5962:2021 — chuẩn quốc tế
- Maintained bởi Linux Foundation từ 2010
- Định dạng: tag-value, JSON, YAML, RDF, spreadsheet
- Mạnh về license metadata (SPDX License List là chuẩn license identifier)
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "my-app-1.0.0",
"documentNamespace": "https://example.com/sbom/my-app-1.0.0",
"creationInfo": {
"created": "2025-06-15T10:00:00Z",
"creators": ["Tool: syft-v1.0.0"]
},
"packages": [
{
"SPDXID": "SPDXRef-Package-lodash",
"name": "lodash",
"versionInfo": "4.17.21",
"downloadLocation": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"filesAnalyzed": false,
"checksums": [{ "algorithm": "SHA256", "checksumValue": "abc123..." }],
"licenseConcluded": "MIT",
"supplier": "Organization: lodash maintainers"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-my-app",
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-Package-lodash"
}
]
}
CycloneDX
- Bắt đầu từ OWASP, light-weight design for security use cases
- Định dạng: JSON, XML, Protocol Buffers
- Tích hợp tốt VEX (Vulnerability Exploitability eXchange)
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:3e671687-...",
"version": 1,
"components": [
{
"type": "library",
"bom-ref": "pkg:npm/lodash@4.17.21",
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21",
"licenses": [{ "license": { "id": "MIT" } }],
"hashes": [{ "alg": "SHA-256", "content": "abc123..." }]
}
],
"dependencies": [
{
"ref": "pkg:npm/my-app@1.0.0",
"dependsOn": ["pkg:npm/lodash@4.17.21"]
}
]
}
So sánh
| Tiêu chí | SPDX | CycloneDX |
|---|---|---|
| Chuẩn hoá | ISO/IEC 5962:2021 | ECMA-424 (đang process) |
| Maintainer | Linux Foundation | OWASP |
| Focus chính | License compliance | Security / vulnerability |
| Format | JSON, YAML, tag-value, RDF | JSON, XML |
| PURL support | Có (từ 2.3) | Có (native) |
| VEX integration | Có | Native |
| Best for | Legal/compliance team | DevSecOps pipeline |
Trong thực tế: dùng cả hai khi cần. Hầu hết tool (syft, Trivy, cdxgen) có thể export cả hai format.
Package URL (PURL)
PURL chuẩn hoá cách định danh package giữa các ecosystem:
pkg:npm/lodash@4.17.21
pkg:pypi/django@4.2.7
pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0
pkg:golang/github.com/gin-gonic/gin@v1.9.1
pkg:docker/library/postgres@15.4
pkg:cargo/serde@1.0.193
Format chung: pkg:<type>/<namespace>/<name>@<version>?<qualifiers>#<subpath>
3. Tạo SBOM với syft
syft là CLI phổ biến nhất, support ~30 ecosystem.
# Cài
brew install syft
# Scan directory (source code)
syft .
# Scan container image
syft alpine:3.19
syft docker.io/myorg/myapp:v1
# Scan và xuất SBOM
syft . -o spdx-json > sbom.spdx.json
syft . -o cyclonedx-json > sbom.cdx.json
syft . -o table # human-readable
# Scan binary
syft /usr/bin/curl
Output mẫu:
NAME VERSION TYPE
async 3.2.4 npm
express 4.18.2 npm
lodash 4.17.21 npm
...
Tạo SBOM với npm built-in (npm 10+)
npm sbom --sbom-format spdx > sbom.spdx.json
npm sbom --sbom-format cyclonedx > sbom.cdx.json
cdxgen — multi-language
npm install -g @cyclonedx/cdxgen
cdxgen -o bom.json -t nodejs .
cdxgen -o bom.json -t python .
cdxgen -o bom.json -t java .
SBOM cho container image
# Build image
docker build -t myapp:v1 .
# Sinh SBOM cho image (gồm cả OS package layer)
syft myapp:v1 -o spdx-json > myapp-sbom.json
# Attach SBOM vào image như attestation (cosign + Sigstore)
cosign attest --predicate myapp-sbom.json \
--type spdxjson \
myapp:v1
4. Dependency scanning — phát hiện CVE
CVE và CVSS basics
Severity scale (CVSS v3.1)
| Score | Severity |
|---|---|
| 0.1 - 3.9 | Low |
| 4.0 - 6.9 | Medium |
| 7.0 - 8.9 | High |
| 9.0 - 10.0 | Critical |
Severity vs Exploitability — khác biệt quan trọng
CVE severity = "Worst case impact NẾU bị exploit"
Exploitability = "Khả năng thực sự exploit trong app của bạn"
Ví dụ: CVE critical trong dep, nhưng:
- App bạn không call function bị vulnerable → exploit không reach được
- App bạn không expose tới untrusted input → attacker không trigger được
- Dep chỉ chạy ở dev env (devDependency) → production không ảnh hưởng
→ Severity 10.0 không tự động = "patch ngay"
Cần đánh giá exploitability cụ thể trong context
Tool theo ecosystem
Node: npm audit, pnpm audit, yarn audit
Python: pip-audit, safety
Ruby: bundle audit
Go: govulncheck (Google official)
Java: OWASP Dependency-Check, Snyk
Rust: cargo audit
Multi: Snyk, GitHub Dependabot, Renovate, Mend (WhiteSource)
npm audit
npm audit
# Output:
# found 5 vulnerabilities (2 moderate, 2 high, 1 critical)
# To address all issues, run:
# npm audit fix
# To address all issues (including breaking changes), run:
# npm audit fix --force
# Chi tiết
npm audit --json
# Chỉ fail trên high+
npm audit --audit-level=high
pip-audit
pip install pip-audit
pip-audit
pip-audit -r requirements.txt
# Output:
# Found 2 known vulnerabilities in 1 package
# Name Version ID Fix Versions
# urllib3 1.26.5 GHSA-mh33-7rrq-662w 1.26.18
govulncheck — chỉ flag vuln EXPLOITABLE
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
# Khác biệt: govulncheck phân tích call graph
# Chỉ báo CVE nếu code thực sự GỌI function bị vulnerable
# → giảm false positive đáng kể
Snyk (commercial)
npm install -g snyk
snyk auth
snyk test # scan deps
snyk container test myapp:v1 # scan container
snyk iac test # scan Terraform/K8s
snyk code test # SAST
5. GitHub Dependabot và Renovate
Dependabot
GitHub native, miễn phí cho mọi repo:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
groups:
minor-and-patch:
update-types: ["minor", "patch"]
ignore:
- dependency-name: "react"
versions: [">=19.0.0"] # tạm hold major
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
Dependabot auto tạo PR khi:
- Có security advisory (urgent, dùng
dependabot/security-updates) - Có version mới (scheduled)
Renovate (Mend) — mạnh hơn
// renovate.json
{
"extends": ["config:base"],
"schedule": ["after 9pm every weekday", "before 5am every weekday"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeType": "branch"
},
{
"matchDepTypes": ["devDependencies"],
"automerge": true
},
{
"matchPackagePatterns": ["^@types/"],
"automerge": true,
"groupName": "@types packages"
}
],
"vulnerabilityAlerts": {
"labels": ["security"],
"schedule": ["at any time"]
}
}
Renovate ưu việt hơn Dependabot:
- Group nhiều update vào 1 PR (giảm noise)
- Auto-merge thông minh hơn
- Schedule chi tiết hơn
- Support nhiều ecosystem hơn (Terraform, Helm, Kustomize)
6. Container image scanning
Trivy — Aqua Security (open source, miễn phí)
Grype + syft (Anchore stack)
brew install grype
# Scan image
grype alpine:3.19
grype myapp:v1
# Scan từ SBOM (offline, fast)
syft myapp:v1 -o json > sbom.json
grype sbom:sbom.json
So sánh Trivy vs Grype
| Trivy | Grype | |
|---|---|---|
| Vendor | Aqua Security | Anchore |
| License | Apache 2.0 | Apache 2.0 |
| Scan types | Image, filesystem, IaC, secret, SBOM | Image, filesystem, SBOM |
| DB sources | NVD, GHSA, OSV, distro advisories | NVD, GHSA, distro advisories |
| Speed | Nhanh, single binary | Nhanh khi dùng với pre-built SBOM |
| IaC scan | Có (Terraform, K8s, Dockerfile) | Không (dùng anchore-engine riêng) |
Khuyến nghị thực tế: Trivy cho one-stop scanning. syft + grype khi cần separation between SBOM generation và scanning (ví dụ: scan offline trong air-gapped env).
7. Tích hợp vào CI
GitHub Actions — fail build trên HIGH+
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "0 6 * * *" # daily 6am UTC
jobs:
deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: npm audit
run: npm audit --audit-level=high
continue-on-error: false
- name: pip-audit
if: hashFiles('requirements.txt') != ''
run: |
pip install pip-audit
pip-audit -r requirements.txt
container:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: "1" # fail build nếu có HIGH/CRITICAL
ignore-unfixed: true # skip CVE chưa có fix
- name: Upload SARIF to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
format: spdx-json
output-file: sbom.spdx.json
- uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.spdx.json
Allowlist khi cần (.trivyignore)
# .trivyignore — ignore CVE chưa có fix hoặc không exploitable
# Mỗi CVE phải có comment lý do và ngày review lại
# CVE-2024-1234: chỉ ảnh hưởng function chúng ta không gọi (path X.Y.Z)
# Review lại: 2026-12-31
CVE-2024-1234
# CVE-2024-5678: dep chỉ dùng trong dev/test
CVE-2024-5678
Quy tắc allowlist:
- Mọi entry phải có expiry date — không allowlist vĩnh viễn
- Comment phải explain vì sao không fix, không chỉ "ignore"
- Review allowlist hàng quý
Branch protection — block merge khi có critical CVE
GitHub repo Settings → Branches → Branch protection rules:
- Require status checks before merge:
✓ Security Scan / deps
✓ Security Scan / container
8. VEX — giảm noise
Vấn đề: scan tool báo 50 CVE — bao nhiêu thực sự ảnh hưởng?
Triage thủ công tốn rất nhiều giờ engineer.
VEX (Vulnerability Exploitability eXchange): document machine-readable
nói "CVE X có trong dep Y nhưng KHÔNG affected vì lý do Z"
CycloneDX VEX example
{
"vulnerabilities": [
{
"id": "CVE-2024-1234",
"source": { "name": "NVD" },
"affects": [{ "ref": "pkg:npm/express@4.18.2" }],
"analysis": {
"state": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"detail": "Vulnerable function `express.unsafeRender` chưa được dùng trong app. Verified bởi static analysis tool X, last reviewed 2025-06-15."
}
}
]
}
state có thể là:
resolved— đã patchexploitable— confirm ảnh hưởngnot_affected— không ảnh hưởng (kèm justification)false_positive— sai từ scan toolin_triage— đang review
VEX cho phép automation: CI thấy CVE → check VEX → nếu có entry not_affected thì skip alert. Giảm false-positive fatigue.
9. Câu hỏi ôn tập
-
Vì sao SBOM trở thành yêu cầu compliance sau sự cố Log4Shell?
Xem đáp án
Khi Log4Shell (CVE-2021-44228) công bố tháng 12/2021, mọi tổ chức phải trả lời câu hỏi đơn giản: "Chúng ta có dùng log4j không? Phiên bản nào? Trong những app nào?" Không có SBOM, đội bảo mật phải scan thủ công hàng trăm app, mất nhiều ngày — trong khi attacker đã exploit hàng giờ sau khi PoC public.
Hệ quả: US Executive Order 14028 (5/2021) yêu cầu SBOM cho mọi software bán cho US gov. EU Cyber Resilience Act (2024) tương tự. SBOM trở thành công cụ chuẩn để incident response, vulnerability management, license compliance, và supply chain audit. Với SBOM, query "ai dùng log4j 2.x" trở thành SQL one-liner thay vì hàng nghìn giờ effort.
-
SPDX và CycloneDX khác nhau như thế nào, và khi nào nên dùng cái nào?
Xem đáp án
SPDX: chuẩn ISO/IEC 5962:2021, maintained bởi Linux Foundation từ 2010. Focus mạnh vào license compliance — SPDX License List là chuẩn license identifier toàn ngành. Định dạng đa dạng (JSON, YAML, RDF, tag-value).
CycloneDX: bắt đầu từ OWASP, light-weight, focus mạnh vào security use case. Native support cho VEX (Vulnerability Exploitability eXchange). Định dạng JSON/XML.
Khi nào dùng:
- SPDX cho legal/compliance team (license audit, US gov submissions yêu cầu SPDX cụ thể)
- CycloneDX cho DevSecOps pipeline (tích hợp scanning, VEX, dễ generate)
- Thực tế: nhiều tool (syft, Trivy) export cả hai, vendor cả hai khi cần.
-
Severity của CVE và exploitability trong app cụ thể khác nhau như thế nào? Cho ví dụ.
Xem đáp án
Severity (CVSS score): đo "worst case impact NẾU CVE bị exploit thành công". Tính trên kịch bản tổng quát, không biết context cụ thể của app.
Exploitability trong app: thực tế CVE có reach được trong app của bạn không, dựa vào: (1) Code vulnerable có được CALL không (call graph analysis)? (2) Untrusted input có chạm tới function đó không? (3) Dep dùng ở runtime hay chỉ dev (devDependency)?
Ví dụ: CVE-2024-XYZ trong thư viện image processing với CVSS 9.8 (Critical) — buffer overflow khi parse exotic image format. Nếu app của bạn chỉ dùng thư viện đó để resize JPG (function khác hoàn toàn) và không bao giờ gọi function vulnerable → exploitability = 0 trong thực tế, dù severity = 9.8.
Đó là lý do tools như
govulncheck(Go) phân tích call graph để chỉ flag CVE actually reachable, giảm false positive. VEX standard giúp document/communicate điều này. -
Vì sao Renovate hoặc Dependabot tự động merge minor/patch update vẫn cần caution?
Xem đáp án
Auto-merge minor/patch dựa trên giả định SemVer chuẩn: minor/patch chỉ thêm tính năng/sửa lỗi, không breaking. Vấn đề:
(1) Maintainer không tuân thủ SemVer: nhiều lib break trong patch (đặc biệt npm ecosystem). Auto-merge có thể đẩy bug breaking lên prod.
(2) Supply chain attack: event-stream và ua-parser-js bị compromise qua patch update. Nếu auto-merge mọi patch, mã độc vào production trong vài giờ.
(3) Behavioral change: lib có thể đổi log format, metrics, default behavior trong "patch" — không breaking API nhưng break monitoring/operations.
Best practice: (a) auto-merge chỉ devDependencies + types, (b) require human review cho runtime deps, (c) chạy full test suite + smoke test trước merge, (d) deploy auto-merge xuống staging trước, soak time vài giờ trước khi roll prod, (e) ưu tiên security alerts (faster lane) nhưng vẫn require status check.
-
Container image scanning với Trivy phát hiện vuln ở những layer nào, và pattern "ignore-unfixed" có ý nghĩa gì?
Xem đáp án
Trivy scan nhiều layer trong image:
- OS package layer: Alpine
apk, Debian/Ubuntudpkg, RHELrpm— check distro advisories - Application dep layer:
node_modules, Python packages, Go modules trong image — check NVD/GHSA - Secrets: hardcoded API key, private key trong layers
- Misconfiguration: Dockerfile bad practices (USER root, COPY .env, etc.)
--ignore-unfixed: skip CVE chưa có version fix nào available. Lý do: nhiều CVE trong distro package (Alpine, Debian) công bố trước khi distro maintainer release patch. Báo những CVE này là "noise actionable" — bạn không thể fix bằng cách update vì update chưa tồn tại. Ignore cho đến khi có fix → giảm alert fatigue và tập trung vào CVE có thể action.Cẩn thận: vẫn cần background scan định kỳ để biết khi nào fix có sẵn để upgrade.
- OS package layer: Alpine
Bài tập thực hành
# 1. Tạo SBOM cho project hiện tại
brew install syft trivy grype
cd ~/your-node-project
syft . -o spdx-json > sbom.spdx.json
syft . -o cyclonedx-json > sbom.cdx.json
syft . -o table # human-readable preview
# 2. Scan với npm audit và pip-audit
npm audit --audit-level=high
# Nếu Python:
pip install pip-audit
pip-audit -r requirements.txt
# 3. Scan SBOM với grype (offline)
grype sbom:sbom.cdx.json
# 4. Build image và scan với Trivy
cat > Dockerfile <<EOF
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "start"]
EOF
docker build -t my-app:test .
trivy image my-app:test
trivy image --severity HIGH,CRITICAL --ignore-unfixed my-app:test
trivy image --format spdx-json my-app:test > image-sbom.json
# 5. Scan Dockerfile và IaC
trivy config Dockerfile
trivy config . # scan toàn bộ Terraform/K8s nếu có
# 6. Setup Dependabot config
mkdir -p .github
cat > .github/dependabot.yml <<EOF
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: docker
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
EOF
git add .github/dependabot.yml
git commit -m "chore: enable Dependabot"
# 7. Trigger Trivy scan trong CI (.github/workflows/security.yml)
# Xem ví dụ trong phần 7 của bài
Tài liệu tham khảo chính thức
- SPDX specification
- CycloneDX specification
- PURL spec
- Trivy documentation
- syft / grype
- GitHub Dependabot
- Renovate
- NVD — National Vulnerability Database
- OSV — Open Source Vulnerabilities
Tiếp theo: Ngày 18 — Quiz Tổng Kết Tuần 3