AppSec 5 — Quản lý bí mật & chuỗi cung ứng
AppSec 5 — Quản lý bí mật & chuỗi cung ứng
Ở bài kiểm soát truy cập & mật mã chúng ta lo về việc ai được làm gì và dữ liệu được mã hoá ra sao. Nhưng có hai điểm mù mà rất nhiều đội bỏ qua cho đến khi bị nạn: thứ nhất, chính những cái khoá dùng để mã hoá và những mật khẩu để kết nối hệ thống lại đang nằm chình ình trong mã nguồn; thứ hai, phần mềm của bạn không chỉ là code bạn viết — 80-90% là thư viện của người khác, và bạn tin tưởng chúng một cách mù quáng.
Bài này đào sâu hai chủ đề gắn liền: quản lý bí mật (secrets management) và bảo mật chuỗi cung ứng (supply chain security). Chủ đề thứ hai phủ đúng hai mục trong OWASP Top 10: A06 — Vulnerable and Outdated Components (thành phần dễ tổn thương & lỗi thời) và A08 — Software and Data Integrity Failures (lỗi về tính toàn vẹn của phần mềm/dữ liệu). Với một ngân hàng, đây không phải chuyện lý thuyết: một khoá truy cập dữ liệu bị lộ hoặc một thư viện bị cấy mã độc có thể mở toang toàn bộ kho dữ liệu khách hàng.
Phần 1 — Quản lý bí mật (secrets management)
Bí mật là gì
Secret (bí mật) là bất kỳ mẩu dữ liệu nào cấp quyền truy cập và phải được giữ kín. Trong một hệ thống dữ liệu ngân hàng điển hình:
| Loại secret | Ví dụ | Nếu lộ thì sao |
|---|---|---|
| Mật khẩu DB | connection string tới PostgreSQL/Oracle | Đọc/ghi toàn bộ dữ liệu giao dịch |
| API key | key gọi cổng thanh toán, dịch vụ eKYC | Mạo danh hệ thống gọi dịch vụ, phát sinh chi phí |
| Khoá mã hoá | AES key, khoá ký JWT | Giải mã dữ liệu at-rest, giả mạo token |
| Token | OAuth access/refresh token, service account | Truy cập tài nguyên thay người dùng/dịch vụ |
| Chứng chỉ & khoá riêng | TLS private key, khoá ký code | Giả mạo server, ký phần mềm giả |
Điểm chung: chúng đều là "chìa khoá". Đánh mất chìa khoá nghĩa là kẻ tấn công không cần phá cửa.
Vấn đề gốc: hardcode secret
Hardcode là nhét thẳng secret vào mã nguồn, file cấu hình, hoặc image. Đây là lỗi phổ biến bậc nhất và cũng nguy hiểm bậc nhất, vì hậu quả lan rộng ngoài tầm kiểm soát:
# ANTI-PATTERN — tuyệt đối KHÔNG làm thế này
DB_PASSWORD = "Ncb@2024_SecretP@ss"
STRIPE_API_KEY = "sk_live_51H9x...4kQ"
conn = connect("postgresql://svc_user:Ncb@2024_SecretP@ss@db-core:5432/core")
Vì sao đặc biệt nguy hiểm:
- Rò rỉ qua git history. Đây là điểm chí tử. Bạn commit secret, hôm sau nhận ra và xoá đi rồi commit lại — nhưng git lưu toàn bộ lịch sử. Secret vẫn nằm nguyên trong commit cũ, ai clone repo cũng lấy được bằng
git log -p. Xoá khỏi HEAD không xoá khỏi history. Với repo public trên GitHub, các bot quét secret tìm ra trong vài phút. - Rò rỉ qua image. Nhét secret vào Dockerfile (kể cả ở layer đã bị "xoá" ở layer sau) → secret vẫn nằm trong layer cache, ai
docker pullvàdocker historyđều thấy. - Không xoay vòng được (rotation). Secret hardcode nằm rải rác khắp nơi. Khi lộ, đổi nó nghĩa là sửa code, build lại, deploy lại toàn bộ — chậm và dễ sót.
- Chung một secret cho mọi môi trường. Dev, staging, prod xài chung một mật khẩu vì nó nằm trong code.
Nguyên tắc vàng: secret không bao giờ thuộc về mã nguồn. Code và cấu hình bí mật phải tách rời hoàn toàn.
Giải pháp theo bậc trưởng thành
Bậc 1 — Biến môi trường (mức tối thiểu)
Đưa secret ra khỏi code, nạp qua biến môi trường lúc chạy:
import os
DB_PASSWORD = os.environ["DB_PASSWORD"] # nạp từ môi trường, không nằm trong code
Đây là mức sàn, đủ để không commit secret vào repo, nhưng vẫn còn hạn chế:
- Biến môi trường dễ rò rỉ: xuất hiện trong
/proc/<pid>/environ, trong log lỗi khi app dump toàn bộ env, trong crash report, trong output củadocker inspect. - Không mã hoá at-rest, không phân quyền chi tiết, không audit ai đã đọc secret.
- File
.envrất hay bị commit nhầm — luôn thêm.envvào.gitignore.
Biến môi trường giải quyết vấn đề "trong code", chứ chưa giải quyết vấn đề "được bảo vệ".
Bậc 2 — Secret manager (chuẩn thực chiến)
Secret manager là dịch vụ lưu trữ bí mật tập trung: HashiCorp Vault, hoặc dịch vụ đám mây như AWS Secrets Manager / Azure Key Vault / GCP Secret Manager, và các KMS (Key Management Service) chuyên cho khoá mã hoá. Ứng dụng không giữ secret mà xin secret lúc chạy qua danh tính đã xác thực.
Lợi ích cốt lõi:
- Lưu tập trung & mã hoá. Mọi secret nằm một chỗ, mã hoá at-rest bằng master key do KMS quản lý. Không rải rác khắp file cấu hình.
- Phân quyền chi tiết (access policy). Service
loan-scoringchỉ đọc được secret của nó, không thấy secret củapayment-gateway. Áp dụng least privilege cho chính secret. - Rotation (xoay vòng). Đổi mật khẩu định kỳ hoặc khi nghi lộ, tập trung một chỗ, không cần deploy lại app.
- Audit log. Ghi lại ai đọc secret nào lúc nào — cực kỳ quan trọng cho ngân hàng khi kiểm toán.
# Minh hoạ — đọc secret từ Vault lúc chạy (không phải SQL, không đánh dấu chạy được)
vault kv get -field=password secret/core-db/svc_user
# App dùng Vault Agent / SDK để lấy động, không lưu vào đĩa
Dynamic secrets — nâng cao
Điểm mạnh đặc trưng của Vault: dynamic secrets (bí mật động). Thay vì lưu sẵn một mật khẩu DB tĩnh, Vault sinh credential mới theo yêu cầu với thời hạn ngắn (TTL, ví dụ 1 giờ). App xin → Vault tạo user DB tạm → hết hạn thì Vault tự thu hồi.
Lợi ích: nếu credential bị lộ, nó chỉ sống vài phút/giờ rồi vô hiệu; mỗi app/instance có credential riêng nên truy vết dễ; không có mật khẩu tĩnh "trường tồn" để đánh cắp. Đây là hình thức least-privilege theo thời gian.
Tránh secret trong log
Một secret được bảo vệ hoàn hảo vẫn có thể rò qua log. Rất hay gặp: app log nguyên connection string khi kết nối lỗi, log toàn bộ HTTP header (chứa Authorization: Bearer ...), hoặc dump toàn bộ biến môi trường khi crash. Nguyên tắc:
- Không log connection string, header xác thực, body request nhạy cảm.
- Che (mask) secret trước khi ghi log:
Authorization: Bearer ***. - Rà soát log tập trung (xem logs & traces) để bảo đảm secret không lọt vào kho log — nơi thường có nhiều người truy cập hơn cả DB.
Secret scanning — hàng rào cuối
Dù kỷ luật đến đâu, sẽ có lúc ai đó lỡ tay commit secret. Secret scanning là quét mã nguồn và git history để phát hiện. Hai công cụ phổ biến:
- gitleaks — quét repo (kể cả toàn bộ history) tìm pattern giống secret (API key, private key, token).
- trufflehog — tương tự, mạnh ở việc xác minh (verify) xem key tìm thấy có còn sống không.
Cách dùng đúng là gắn vào hai điểm:
- Pre-commit hook — quét trước khi commit, chặn secret ngay trên máy lập trình viên, không cho nó vào history.
- CI pipeline — quét lại ở CI/CD, fail build nếu phát hiện secret. Đây là chốt chặn cuối cho những gì lọt qua bậc 1.
# Minh hoạ — quét repo trước khi push
gitleaks detect --source . --redact
# Trong CI: exit code != 0 => chặn merge
Lưu ý quan trọng: nếu secret đã lọt vào history, quét ra rồi thì việc đầu tiên không phải xoá history mà là rotate ngay lập tức — coi như secret đó đã lộ vĩnh viễn. Dọn history chỉ là dọn dẹp thứ cấp.
Phần 2 — Bảo mật chuỗi cung ứng (A06 + A08)
Vì sao chuỗi cung ứng là điểm yếu
Một ứng dụng hiện đại là một cây phụ thuộc khổng lồ. Bạn viết vài nghìn dòng, nhưng npm install hay pip install kéo về hàng trăm package, mỗi package lại kéo package con — transitive dependencies (phụ thuộc bắc cầu). Bạn đang tin tưởng mã của hàng trăm người lạ. Đây là bề mặt tấn công mà OWASP xếp thành hai mục:
- A06 — Vulnerable and Outdated Components. Bạn dùng thư viện có lỗ hổng đã biết (CVE) hoặc lỗi thời không còn vá.
- A08 — Software and Data Integrity Failures. Bạn tin và chạy code/artifact mà không xác minh tính toàn vẹn — không biết nó có bị đánh tráo, cấy mã độc hay không.
Các dạng rủi ro chuỗi cung ứng
| Rủi ro | Bản chất | Ví dụ |
|---|---|---|
| Thư viện có lỗ hổng | Package hợp lệ nhưng chứa CVE nghiêm trọng | Log4Shell (CVE-2021-44228) — lỗ hổng RCE trong Log4j, chấn động toàn ngành |
| Dependency confusion | Kẻ tấn công publish package public trùng tên package nội bộ, số version cao hơn → build kéo nhầm bản độc | Trùng tên internal-utils của công ty |
| Typosquatting | Package độc đặt tên gần giống package thật để lừa gõ nhầm | reqeusts thay vì requests |
| Malicious package | Package cố tình cấy mã độc (đánh cắp env, cài backdoor) | Package bị chiếm quyền maintainer rồi cập nhật bản độc |
Log4Shell là bài học kinh điển: một dòng log tưởng vô hại kích hoạt thực thi mã từ xa, và vì Log4j nằm sâu trong hàng vạn ứng dụng qua transitive dependency, nhiều đội không biết mình có dùng nó hay không. Bài học rút ra chính là cả hai công cụ sau.
SCA — quét thành phần (giải A06)
SCA (Software Composition Analysis) là quét toàn bộ cây phụ thuộc, đối chiếu với cơ sở dữ liệu lỗ hổng để tìm CVE. Công cụ phổ biến:
- Dependabot (GitHub) — tự động phát hiện dependency có lỗ hổng và mở PR nâng version.
- Snyk — SCA thương mại, quét dependency + container, gợi ý bản vá.
- OWASP Dependency-Check — công cụ mã nguồn mở, đối chiếu với NVD.
SCA trả lời câu hỏi "tôi đang dùng thư viện nào có lỗ hổng đã biết?" — chính là câu hỏi mà đội nào cũng bí lúc Log4Shell nổ ra.
# Minh hoạ — chạy Dependency-Check trong CI (không phải SQL)
dependency-check --project core-api --scan ./ --failOnCVSS 7
# CVSS >= 7 (High) => fail build
SBOM — kê khai thành phần
SBOM (Software Bill of Materials — bảng kê khai vật liệu phần mềm) là danh sách đầy đủ mọi thành phần trong sản phẩm: tên, version, license, hash. Giống như bảng thành phần dinh dưỡng trên hộp sữa — bạn biết chính xác bên trong có gì.
Vì sao cần: khi một CVE mới công bố (như Log4Shell), thay vì cuống cuồng dò từng repo, bạn tra SBOM để biết ngay sản phẩm nào chứa thành phần bị ảnh hưởng. Định dạng chuẩn: CycloneDX, SPDX. SBOM thường được sinh tự động trong pipeline và lưu kèm mỗi bản release.
Bảo đảm tính toàn vẹn (giải A08)
A08 nói về tin tưởng có xác minh. Các biện pháp:
Pin version & lockfile. Đừng khai requests>=2 (mỗi lần build kéo bản khác nhau, không lường trước). Hãy ghim (pin) version chính xác và commit lockfile (package-lock.json, poetry.lock, requirements.txt có hash). Lockfile khoá cả version và hash — build lại luôn ra đúng cùng một tập phụ thuộc, và nếu hash không khớp (bị đánh tráo) thì build fail. Đây là lá chắn trực tiếp chống dependency confusion.
Ký & xác minh artifact. Khi build ra artifact (image, package), ký số nó; khi deploy, xác minh chữ ký trước khi chạy. Dự án Sigstore với công cụ cosign cho phép ký/xác minh container image dễ dàng. Nếu artifact bị đánh tráo giữa build và deploy, chữ ký không khớp → chặn.
# Minh hoạ — ký và xác minh image bằng cosign (không phải SQL)
cosign sign registry.ncb.local/core-api:1.4.2
cosign verify registry.ncb.local/core-api:1.4.2 # chạy ở bước deploy
Provenance & SLSA. Provenance là bằng chứng có kiểm chứng về xuất xứ của artifact: nó được build từ commit nào, bằng pipeline nào, lúc nào. SLSA (Supply-chain Levels for Software Artifacts — đọc là "salsa") là bộ khung phân cấp độ tin cậy chuỗi cung ứng, từ mức 1 (có provenance) đến các mức cao (build cách ly, không thể giả mạo). Nó chuẩn hoá câu hỏi "artifact này thực sự đến từ đâu?".
Cập nhật vá kịp thời. Có SCA/SBOM mà không vá thì vô nghĩa. Cần quy trình theo dõi CVE và vá theo mức độ nghiêm trọng (critical vá trong ngày). Đây là phần "Outdated" của A06.
Bảo vệ pipeline CI/CD — nguồn tin cậy. Chuỗi cung ứng gồm cả chính pipeline. Nếu kẻ tấn công chiếm được CI, chúng cấy mã vào mọi bản build (như vụ SolarWinds). Nguyên tắc: chỉ build từ nguồn tin cậy, khoá quyền vào pipeline, quản secret của CI bằng chính secret manager ở Phần 1, và ký artifact ngay trong pipeline để tạo chuỗi tin cậy khép kín.
Sơ đồ: chuỗi cung ứng phần mềm & điểm tấn công
Use case thực tế
Bối cảnh. Đội dữ liệu NCB vận hành service loan-scoring chấm điểm tín dụng, kết nối kho dữ liệu khách hàng và một API eKYC bên ngoài. Rà soát an ninh phát hiện hai vấn đề.
Vấn đề 1 — secret hardcode. Trong repo, connection string tới DB core và API key eKYC được nhét thẳng vào config.py. Chạy gitleaks detect --source . phát hiện chúng nằm trong history từ 8 tháng trước, và repo này được share cho 14 developer. Xử lý:
- Rotate ngay cả mật khẩu DB và API key eKYC — coi như đã lộ (14 người + toàn bộ history đã có).
- Chuyển secret sang Vault, app xin động qua Vault Agent với policy chỉ cho
loan-scoringđọc. - Bật rotation tự động 30 ngày cho mật khẩu DB; bật audit log Vault.
- Gắn
gitleaksvào pre-commit hook + CI, fail build nếu phát hiện secret.
Vấn đề 2 — dependency lỗ hổng. Chạy OWASP Dependency-Check trên service, phát hiện một thư viện parse XML có CVE CVSS 8.1 (High) và một package logging bản cũ dính lỗ hổng tương tự Log4Shell. Xử lý:
- Sinh SBOM (CycloneDX) cho service, lưu kèm release để lần sau tra cứu nhanh.
- Bật Dependabot tự mở PR nâng version; vá hai thư viện High trong 48 giờ.
- Pin toàn bộ version + commit lockfile có hash; thêm bước SCA vào CI với ngưỡng
failOnCVSS 7. - Ký image bằng
cosignkhi build, xác minh chữ ký ở bước deploy để chống đánh tráo.
Kết quả. Không còn secret trong code hay history đang hiệu lực; mọi truy cập secret có audit; hai lỗ hổng High được vá; build production chỉ chạy artifact đã ký và đã qua SCA. Bề mặt tấn công qua secret và chuỗi cung ứng thu hẹp rõ rệt, và quan trọng hơn — lần CVE tới, đội tra SBOM là biết ngay có ảnh hưởng hay không thay vì hoảng loạn dò tay.
Ghi nhớ
- Secret không bao giờ thuộc về mã nguồn. Hardcode secret rò rỉ qua git history (xoá HEAD không xoá history) và qua image layer — cực phổ biến và nguy hiểm.
- Bậc trưởng thành: biến môi trường (mức sàn, vẫn dễ rò qua log/env dump) → secret manager (Vault/KMS: tập trung, mã hoá, phân quyền, rotation, audit) → dynamic secrets (credential ngắn hạn tự thu hồi).
- Tránh secret trong log: che connection string, header
Authorization, không dump env khi crash. - Secret scanning (gitleaks/trufflehog) gắn ở pre-commit + CI để chặn. Nếu đã lộ, việc đầu tiên là rotate, không phải dọn history.
- Chuỗi cung ứng = A06 (thành phần lỗ hổng/lỗi thời) + A08 (tính toàn vẹn). 80-90% code là của người khác.
- Rủi ro: CVE (Log4Shell), dependency confusion, typosquatting, malicious package.
- Công cụ: SCA (Dependabot/Snyk/OWASP Dependency-Check) tìm CVE; SBOM (CycloneDX/SPDX) kê khai thành phần để tra cứu nhanh khi có CVE mới.
- Toàn vẹn: pin version + lockfile có hash, ký & xác minh artifact (Sigstore/cosign), provenance & SLSA, vá kịp thời, và bảo vệ chính pipeline CI/CD.
- Bối cảnh ngân hàng: đừng để lộ khoá truy cập dữ liệu; kiểm soát chặt mọi thư viện đưa vào hệ thống.
Bài viết liên quan
Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.
Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.
Vì sao cần orchestration; Pod, Deployment, Service, Ingress; scaling, self-healing và cấu hình.
Bản chất DevOps: phá bỏ rào cản Dev–Ops, vòng lặp vô tận, CALMS, và vì sao tự động hoá.