Mở đầu — vì sao bài này quan trọng
Có một sự thật hơi phũ phàng mà bạn cần chấp nhận sớm trong nghề Operations: gần như mọi lỗ hổng bảo mật nghiêm trọng dẫn tới rò rỉ dữ liệu đều là lỗ hổng đã được biết và đã có bản vá từ lâu. Kẻ tấn công hiếm khi cần tới zero-day (lỗ hổng chưa ai biết) để phá được hệ thống của bạn. Chúng chỉ cần một server chạy phiên bản OpenSSL cũ, một thư viện npm bị bỏ quên 3 năm, hoặc một container base image kéo từ registry năm ngoái mà chưa ai rebuild.
Patch Management (quản lý vá lỗi) và Vulnerability Scanning (quét lỗ hổng) chính là hai công việc "phòng bệnh" đối trọng với công việc "chữa cháy" mà bạn học ở các bài Incident Management. Điểm khác biệt cốt lõi: Incident xảy ra rồi mới xử lý, còn patch management là bạn chủ động đóng cửa trước khi kẻ trộm gõ cửa. Trong bối cảnh Việt Nam, nơi rất nhiều startup và doanh nghiệp vừa chạy hạ tầng trên VPS tự quản lý hoặc cloud nhưng thiếu người chuyên trách bảo mật, thì việc một kỹ sư Ops nắm vững patch management thường là tuyến phòng thủ đầu tiên và duy nhất của cả công ty.
Bài này sẽ giúp bạn xây dựng một tư duy hệ thống về vá lỗi trên bốn tầng: hệ điều hành (OS), image container, thư viện phụ thuộc (dependency), và ứng dụng — cùng cách dùng công cụ quét để biết mình đang "nợ" bao nhiêu lỗ hổng.
Khái niệm cốt lõi
CVE và điểm CVSS — ngôn ngữ chung của lỗ hổng
Trước khi vá, bạn phải biết mình đang vá cái gì. CVE (Common Vulnerabilities and Exposures) là mã định danh toàn cầu cho mỗi lỗ hổng đã công bố, ví dụ CVE-2021-44228 chính là Log4Shell nổi tiếng. Mỗi CVE được chấm điểm CVSS (Common Vulnerability Scoring System) từ 0 đến 10:
- 0.1–3.9: Low
- 4.0–6.9: Medium
- 7.0–8.9: High
- 9.0–10.0: Critical
Bốn tầng cần vá
Sai lầm phổ biến nhất là nghĩ "patch" chỉ là chạy apt upgrade. Thực tế một ứng dụng hiện đại có bốn tầng phải quản lý riêng:
1. OS patches (Ubuntu, Amazon Linux). Đây là nhân hệ điều hành và các gói hệ thống như OpenSSL, glibc, systemd, kernel. Trên Ubuntu bạn dùng apt, trên Amazon Linux / RHEL dùng yum/dnf. Điểm đau lớn nhất ở tầng này là kernel patch cần reboot, mà reboot server production thì phải có kế hoạch. Ubuntu có unattended-upgrades để tự cài bản vá bảo mật, và Ubuntu Pro có Livepatch để vá kernel mà không cần reboot.
2. Container base image (Debian slim, Alpine). Nếu app của bạn chạy trong Docker, thì mọi lỗ hổng trong base image python:3.11-slim hay node:20-alpine đều là lỗ hổng của bạn. Quan trọng: bạn không patch một container đang chạy — bạn rebuild image với base mới rồi redeploy. Alpine nhẹ và ít lỗ hổng hơn do dùng musl libc và ít gói, nhưng đôi khi gây rắc rối tương thích với các thư viện Python/Node biên dịch sẵn cho glibc.
3. Dependency (npm, pip, maven). Đây thường là tầng có nhiều lỗ hổng nhất vì một app Node điển hình kéo về hàng nghìn package transitive (phụ thuộc gián tiếp). Công cụ như npm audit, pip-audit, hoặc Dependabot/Renovate giúp bạn thấy và tự động nâng phiên bản. Chìa khóa là phân biệt direct dependency (bạn khai báo) và transitive dependency (thư viện của thư viện) — phần lớn CVE nằm ở transitive.
4. Application. Chính code bạn viết. Tầng này không "quét ra CVE" theo kiểu thư viện, mà cần SAST (Static Application Security Testing — quét mã tĩnh) và DAST (Dynamic — quét ứng dụng đang chạy) để phát hiện lỗi như SQL injection, hardcoded secret, XSS. Vá ở tầng này nghĩa là sửa code.
SBOM — biết mình đang chạy cái gì
Bạn không thể vá thứ bạn không biết mình đang chạy. SBOM (Software Bill of Materials — "hóa đơn nguyên vật liệu phần mềm") là danh sách đầy đủ mọi thành phần trong artifact của bạn, theo chuẩn như SPDX hay CycloneDX. Khi Log4Shell nổ ra, các công ty có SBOM chỉ mất vài phút để trả lời "chúng ta có dùng Log4j ở đâu không?", còn công ty không có thì mất nhiều ngày grep thủ công khắp mọi repo.
Patch cadence và Patch Window
Không phải vá càng nhanh càng tốt một cách vô tổ chức. Bạn cần một cadence (nhịp vá định kỳ): ví dụ Critical vá trong 24–48h, High trong 7 ngày, Medium trong 30 ngày. Và bạn cần patch window (khung giờ bảo trì được duyệt trước) để áp bản vá cần reboot vào giờ thấp điểm. Nội dung này giao thoa với Change Management (Bài 15) — patch cũng là một loại change và cần đi qua quy trình risk tier tương ứng.
Tình huống thực tế
Ví dụ 1 — Log4Shell và cái giá của việc không có SBOM
Tháng 12/2021, CVE-2021-44228 (Log4Shell, CVSS 10.0) bùng nổ toàn cầu. Một fintech giả định ở TP.HCM — gọi là VíNhanh — có khoảng 40 microservice Java. Đội Ops nhận cảnh báo lúc 2 giờ sáng nhưng không ai trả lời được câu hỏi đơn giản: "Service nào của mình dùng Log4j phiên bản dính lỗi?".
Không có SBOM, họ phải cử 3 kỹ sư grep thủ công qua từng repo Maven, kiểm tra cả transitive dependency vì nhiều service không khai báo Log4j trực tiếp mà bị kéo vào qua một thư viện logging framework khác. Mất gần 18 tiếng để lập được danh sách, thêm 2 ngày để nâng cấp và redeploy tất cả. Trong khoảng thời gian đó, WAF (tường lửa ứng dụng) được cấu hình chặn tạm các payload ${jndi: như một biện pháp giảm thiểu tạm thời.
Bài học: Nếu VíNhanh đã tạo SBOM (CycloneDX) trong pipeline CI cho mỗi build, họ chỉ cần một câu query để biết chính xác 11/40 service bị ảnh hưởng, thu hẹp bán kính và vá trong nửa ngày. SBOM không ngăn được lỗ hổng, nhưng nó biến một cuộc khủng hoảng mù mờ thành một danh sách công việc rõ ràng.
Ví dụ 2 — Container "sạch" nhưng base image mục ruỗng
Một công ty thương mại điện tử ở Hà Nội deploy khoảng 60 image lên Kubernetes. Code do đội dev viết rất cẩn thận, npm audit sạch bong. Nhưng khi họ bật Trivy scan trong pipeline lần đầu, kết quả gây sốc: mỗi image có trung bình 90–140 lỗ hổng High/Critical, hầu hết đến từ base image node:16 (bản full, không phải slim) mà họ đã pin từ 14 tháng trước và chưa bao giờ rebuild.
Vấn đề mấu chốt: họ nghĩ "image đã build xong là bất biến nên an toàn". Thực tế bất biến nghĩa là các lỗ hổng cũng bị đóng băng vĩnh viễn trong đó. Giải pháp gồm ba việc: (1) chuyển sang node:20-slim giảm ngay số gói OS, (2) thêm bước rebuild định kỳ hàng tuần để mọi image luôn kéo bản vá OS mới nhất kể cả khi code không đổi, (3) đặt cổng chặn (gate) trong CI: build fail nếu có CVE Critical chưa được whitelist. Sau một tháng, số lỗ hổng trung bình mỗi image giảm còn dưới 10.
Bài học: Container image không tự vá lấy nó. "Immutable" là về triển khai, không phải về bảo mật. Phải có cơ chế rebuild định kỳ, nếu không image của bạn già đi mỗi ngày.
Ví dụ 3 — Bản vá vội vàng gây sự cố lớn hơn lỗ hổng
Một startup SaaS ở Singapore phục vụ khách Đông Nam Á nhận cảnh báo một CVE High trên thư viện xử lý ảnh. Đội Ops, vì sợ, đã áp npm update toàn bộ dependency ngay giữa giờ làm việc và deploy thẳng lên production, không qua staging. Bản nâng cấp kéo theo một breaking change ở API của một thư viện khác, khiến chức năng upload ảnh chết hoàn toàn trong 3 tiếng — thiệt hại thực tế còn lớn hơn nhiều so với rủi ro của chính CVE (vốn chỉ khai thác được trong điều kiện rất hẹp).
Bài học: Patch là một loại change và phải đi qua quy trình change management. Nâng cấp có mục tiêu (chỉ package bị dính, dùng npm audit fix thay vì npm update bừa bãi), test ở staging, và chọn patch window. Tốc độ vá phải cân với rủi ro của chính bản vá — đây là bài học kinh điển giữa "rủi ro bảo mật" và "rủi ro vận hành".
Hướng dẫn từng bước
Dưới đây là quy trình thiết lập một chương trình patch management cơ bản mà bạn có thể áp dụng cho một team nhỏ:
Bước 1 — Kiểm kê (Inventory). Lập danh sách mọi thứ cần vá: bao nhiêu server OS, image gì, ngôn ngữ/package manager nào. Không có inventory thì mọi bước sau đều vô nghĩa. Với cloud, dùng tag để phân loại theo môi trường (prod/staging) và mức độ quan trọng.
Bước 2 — Bật quét ở đúng chỗ. Tích hợp scanner theo tầng:
- OS & container:
TrivyhoặcGrypequét image trong pipeline CI, kết hợp registry scan (ECR scan on push, Harbor + Trivy). - Dependency:
npm audit/pip-audit/mvn dependency-checktrong CI; bật Dependabot hoặc Renovate trên repo. - Application: thêm một SAST như
Semgrephoặc CodeQL vào pull request.
trivy sbom hoặc syft để sinh SBOM CycloneDX cho mỗi build và lưu lại làm artifact. Đây là "bảo hiểm" cho lần khủng hoảng tiếp theo.Bước 4 — Định nghĩa SLA vá theo mức độ. Viết thành policy rõ ràng, ví dụ: Critical → 48h, High → 7 ngày, Medium → 30 ngày, Low → best effort. Gắn với ticket tự động khi scanner phát hiện.
Bước 5 — Đặt cổng chặn (gate) trong CI/CD. Cấu hình build fail khi có CVE Critical mới chưa được xử lý hoặc whitelist. Cho phép whitelist có thời hạn (ví dụ tối đa 30 ngày) kèm lý do, để tránh việc "tạm bỏ qua" trở thành vĩnh viễn.
Bước 6 — Tự động hóa OS patch. Bật unattended-upgrades cho bản vá bảo mật trên Ubuntu, hoặc dùng AWS Systems Manager Patch Manager với maintenance window cho fleet EC2. Thiết kế để reboot theo nhóm (rolling) tránh downtime.
Bước 7 — Rebuild định kỳ. Đặt một cron pipeline hàng tuần rebuild toàn bộ image từ base mới, kể cả khi code không đổi, rồi cho chạy qua bộ test hồi quy.
Bước 8 — Đo và báo cáo. Theo dõi các chỉ số: số CVE tồn đọng theo mức độ, thời gian trung bình từ lúc CVE công bố đến lúc vá xong (mean time to remediate), tỷ lệ image quá hạn rebuild. Đưa lên dashboard để lãnh đạo thấy được "nợ bảo mật".
Lỗi thường gặp & mẹo
Lỗi: Chỉ vá tầng mình quen thuộc. Dev quét npm audit sạch rồi yên tâm, quên mất base image đầy lỗ hổng OS. Ops vá OS chăm chỉ nhưng bỏ quên dependency. Phải phủ cả bốn tầng.
Lỗi: Cảnh báo quá nhiều dẫn tới tê liệt (alert fatigue). Bật scanner mà không lọc theo severity và reachability sẽ tạo hàng nghìn dòng vô nghĩa, khiến team bỏ qua tất cả. Mẹo: bắt đầu chỉ gate ở Critical, ưu tiên CVE có exploit thực tế đã được biết (tham chiếu danh sách KEV của CISA — Known Exploited Vulnerabilities).
Lỗi: Whitelist vĩnh viễn. Ai đó "tạm bỏ qua" một CVE để kịp deadline rồi không bao giờ quay lại. Mẹo: mọi ngoại lệ phải có ngày hết hạn và tự động cảnh báo khi quá hạn.
Lỗi: Vá trực tiếp lên container đang chạy. apt upgrade bên trong pod đang chạy là chống chỉ định — thay đổi biến mất khi pod restart và phá vỡ tính bất biến. Luôn rebuild image.
Mẹo: Pin phiên bản có kiểm soát. Dùng node:20.11.1-slim thay vì node:latest để build tái lập được, nhưng phải kèm cơ chế rebuild định kỳ để không bị đóng băng lỗ hổng.
Mẹo: Tách bạch việc phát hiện và việc sửa. Scanner cho bạn danh sách; đừng tự động deploy bản vá thẳng production. Để Dependabot mở PR, để CI test, để con người review với change nhỏ.
Mẹo: Multi-stage build giúp giảm bề mặt tấn công — build trong image đầy đủ công cụ, chạy trên image runtime tối giản (distroless) không có shell, không có package manager, ít CVE hơn hẳn.
Bài tập thực hành
- Quét một image thật. Cài Trivy và chạy
trivy image node:16rồitrivy image node:20-slim. So sánh số lỗ hổng Critical/High giữa hai image và viết ra 3 kết luận về lý do chênh lệch.
- Tạo SBOM. Với một project Node hoặc Python bất kỳ, sinh SBOM bằng
syfthoặctrivy sbom, mở file ra và tự trả lời: có bao nhiêu direct dependency và bao nhiêu transitive?
- Thiết kế policy. Viết một bảng SLA vá lỗi cho công ty giả định của bạn: 4 mức severity, thời hạn vá tương ứng, và ai chịu trách nhiệm cho từng tầng (OS/container/dependency/app).
- Mô phỏng ra quyết định. Cho tình huống: một CVE Critical trên thư viện chỉ dùng ở môi trường dev, không có trong build production. Bạn xử lý ưu tiên thế nào và tại sao? Viết 5–7 câu lập luận dựa trên reachability và bối cảnh triển khai.
Tóm tắt
Patch Management và Vulnerability Scanning là công việc phòng ngừa nền tảng của mọi kỹ sư Operations. Hãy nhớ những điểm cốt lõi:
- Phần lớn vi phạm bảo mật đến từ lỗ hổng đã biết chưa được vá, không phải zero-day.
- Có bốn tầng phải quản lý riêng: OS, container base image, dependency, và application — bỏ sót bất kỳ tầng nào đều để lại cửa mở.
- CVE/CVSS giúp ưu tiên, nhưng phải cân với reachability và bối cảnh expose thực tế.
- SBOM là bảo hiểm cho khủng hoảng tiếp theo — biết mình chạy cái gì mới vá được nhanh.
- Container không tự vá; phải rebuild định kỳ, không sửa pod đang chạy.
- Patch là một loại change — vá có mục tiêu, test ở staging, dùng patch window; đừng để bản vá vội vàng gây sự cố lớn hơn chính lỗ hổng.
- Tự động hóa việc phát hiện, đặt gate ở CI cho Critical, đo thời gian vá, và quản lý ngoại lệ có thời hạn.