Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là QA của một fintech ở TP.HCM. Đội dev vừa merge một tính năng thanh toán mới, test chức năng chạy xanh 100%, PM hài lòng, sản phẩm lên production tối thứ Sáu. Đến sáng thứ Hai, một researcher gửi email báo rằng endpoint /api/user/{id} trả về thông tin của bất kỳ user nào chỉ cần đổi số ID trên URL. Đó là lỗ hổng IDOR (Insecure Direct Object Reference) — và không một test case chức năng nào của bạn bắt được nó, vì về mặt chức năng thì API hoạt động "đúng".
Đây chính là khoảng trống mà security testing automation lấp vào. Trong ngành QA hiện đại, bảo mật không còn là việc của riêng đội pentest cuối kỳ. Với tốc độ release hàng ngày, bạn không thể chờ một đợt penetration test thủ công mỗi quý — lúc đó lỗ hổng đã nằm trên production nhiều tuần. Thay vào đó, chúng ta đưa các công cụ quét bảo mật tự động vào ngay trong pipeline, chạy song song với test chức năng.
Bài này tập trung vào hai trụ cột nền tảng của security automation mà một SDET cần nắm: SAST (quét mã nguồn tĩnh) và DAST (quét ứng dụng đang chạy động). Đây không phải để biến bạn thành hacker, mà để bạn tự tin cắm được lớp phòng thủ bảo mật vào quy trình test hiện có, và biết cách đọc — lọc — hành động trên kết quả quét. Trong bối cảnh Nghị định 13/2023 về bảo vệ dữ liệu cá nhân đã có hiệu lực ở Việt Nam, kỹ năng này càng trở thành lợi thế cạnh tranh rõ rệt.
Khái niệm cốt lõi
SAST — Static Application Security Testing
SAST quét mã nguồn (hoặc bytecode) để tìm lỗ hổng trước khi ứng dụng compile hay chạy. Bạn có thể hình dung SAST như một người soát chính tả cực kỳ am hiểu về bảo mật: nó đọc từng dòng code và cảnh báo "chỗ này bạn nối chuỗi SQL trực tiếp từ input người dùng — nguy cơ SQL Injection".
Đặc điểm của SAST:
- White-box: nhìn thấy toàn bộ code, hiểu cấu trúc bên trong.
- Chạy sớm (shift-left): có thể chạy ngay trong IDE, pre-commit hook, hoặc CI khi mở pull request.
- Bắt được lỗi ở dòng cụ thể: nó chỉ đúng file, đúng số dòng.
- Điểm yếu — false positive cao: SAST không biết dữ liệu có thực sự đến từ nguồn nguy hiểm hay không, nên hay báo động giả. Nó cũng không thấy lỗi cấu hình runtime hay lỗi logic nghiệp vụ.
Ví dụ chạy Semgrep:
Cài đặt
pip install semgrepQuét toàn bộ project với ruleset bảo mật có sẵn
semgrep --config=auto .Quét theo ruleset OWASP Top 10 cho một ngôn ngữ cụ thể
semgrep --config="p/owasp-top-ten" ./srcXuất kết quả dạng JSON để pipeline đọc
semgrep --config=auto --json --output=semgrep-report.json .
Semgrep còn cho bạn viết rule riêng bằng YAML, ví dụ chặn việc log mật khẩu:
rules:
- id: no-hardcoded-password
pattern: password = "..."
message: "Không hardcode mật khẩu trong source code"
languages: [python]
severity: ERROR
DAST — Dynamic Application Security Testing
DAST quét ứng dụng đang chạy thật từ bên ngoài, giống như một hacker thăm dò website của bạn. Nó không cần và không nhìn thấy source code — nó gửi request độc hại (payload) tới các endpoint và quan sát phản hồi để phát hiện lỗ hổng.
Đặc điểm của DAST:
- Black-box: chỉ tương tác qua HTTP/HTTPS như người dùng bên ngoài.
- Chạy muộn hơn: cần một môi trường staging đã deploy để quét.
- Bắt được lỗi runtime thật: XSS, SQL Injection thực sự khai thác được, lỗi cấu hình header, session yếu, HTTPS sai.
- Ít false positive về khả năng khai thác (vì nó thử tấn công thật), nhưng false negative cao (không quét được code chưa được request chạm tới).
Ví dụ chạy OWASP ZAP dạng automation bằng Docker:
Baseline scan — nhanh, thụ động, hợp cho chạy mỗi lần deploy
docker run -t owasp/zap2docker-stable zap-baseline.py \
-t https://staging.myapp.vn -r zap-report.htmlFull scan — chủ động tấn công, chạy chậm, hợp cho nightly
docker run -t owasp/zap2docker-stable zap-full-scan.py \
-t https://staging.myapp.vn -r zap-full-report.html
SAST vs DAST — hai mặt bổ trợ nhau
Đừng chọn một, hãy dùng cả hai. Chúng bù đắp điểm mù của nhau:
| Tiêu chí | SAST | DAST |
|---|---|---|
| Nhìn thấy | Source code | Ứng dụng đang chạy |
| Thời điểm | Rất sớm (commit/PR) | Sau khi deploy staging |
| Loại lỗi mạnh | Code pattern nguy hiểm | Lỗ hổng khai thác thực tế |
| Điểm yếu | False positive cao | Không thấy code chưa chạy |
| Cần môi trường | Không | Có (staging) |
npm audit). Đây là mảng riêng nhưng thường được gộp chung vào chiến lược security automation vì hầu hết lỗ hổng thực tế đến từ dependency chứ không phải code bạn tự viết.Tình huống thực tế
Tình huống 1 — Sàn TMĐT và lỗ hổng thư viện log4j
Một sàn thương mại điện tử tầm trung ở Hà Nội (giả định gọi là ShopViet) có team QA 6 người, backend viết bằng Java Spring Boot. Cuối 2021, khi lỗ hổng Log4Shell (CVE-2021-44228) bùng nổ, cả team hốt hoảng vì không ai biết dịch vụ nào của mình đang dùng log4j phiên bản dính lỗi. Họ mất trọn 2 ngày để dò thủ công qua hàng chục microservice.
Sau sự cố, QA lead quyết định cắm Trivy (SCA) và Semgrep (SAST) vào pipeline. Từ đó, mỗi PR đều tự động quét dependency. Ba tháng sau, Trivy chặn đứng một PR đưa thư viện jackson-databind phiên bản có CVE deserialization vào codebase — trước khi nó kịp lên production.
Bài học: Phần lớn rủi ro bảo mật không nằm ở code bạn viết, mà ở thư viện bạn kéo về. Một SCA scan tự động chỉ mất vài phút cấu hình nhưng cứu bạn khỏi những đêm mất ngủ. SAST và SCA nên là "cổng chặn" bắt buộc trong CI, không phải tùy chọn.
Tình huống 2 — Fintech và endpoint IDOR bị DAST bỏ sót một nửa
Quay lại ví dụ fintech đầu bài. Sau sự cố IDOR, team cắm OWASP ZAP baseline scan vào pipeline staging. Nhưng vài tuần sau, một lỗ hổng XSS ở trang admin lại lọt lưới. Lý do: ZAP baseline chạy ở chế độ thụ động và không đăng nhập được vào khu vực admin — nó chỉ quét được phần public.
Họ sửa bằng cách cấu hình ZAP với authentication context (nạp sẵn session token của tài khoản test), rồi chuyển sang full scan chạy ban đêm. Ngay lần chạy đầu tiên có auth, ZAP tìm ra 3 điểm reflected XSS trong khu vực admin mà baseline scan mù hoàn toàn.
Bài học: DAST chỉ tốt bằng phạm vi mà nó chạm được. Nếu công cụ không đăng nhập được, không đi qua được các luồng phức tạp (giỏ hàng, thanh toán nhiều bước), thì cả một nửa ứng dụng nằm ngoài tầm quét. Đầu tư cấu hình authentication cho DAST là bước quan trọng bậc nhất, đừng bỏ qua vì thấy phiền.
Tình huống 3 — Startup và cơn ác mộng false positive
Một startup SaaS ở Đà Nẵng bật SonarQube với ngưỡng khắt khe ngay từ đầu. Kết quả: lần quét đầu tiên ra hơn 400 security issue. Dev nhìn con số này liền... phớt lờ toàn bộ, vì phần lớn là false positive (ví dụ báo "hardcoded password" cho một biến tên passwordLabel = "Mật khẩu"). Chỉ sau một tuần, cả team coi cảnh báo security như spam.
QA lead phải làm lại từ đầu: tắt bớt rule gây nhiễu, chỉ giữ lại nhóm rule severity cao và thực sự liên quan, đặt baseline "chỉ chặn issue MỚI" thay vì bắt sửa hết nợ cũ. Số cảnh báo giảm còn ~15 issue thật sự đáng quan tâm. Lúc này dev mới thực sự đọc và sửa.
Bài học: Kẻ thù lớn nhất của security automation không phải là thiếu công cụ, mà là alert fatigue — mệt mỏi vì cảnh báo giả. Một công cụ báo 400 lỗi mà bị bỏ qua còn tệ hơn công cụ báo 15 lỗi được sửa. Tinh chỉnh (tuning) quan trọng ngang việc cài đặt.
Hướng dẫn từng bước
Dưới đây là lộ trình cắm security automation vào một dự án thực tế, đi từ dễ tới khó:
Bước 1 — Bắt đầu với SCA, vì nó cho ROI cao nhất.
Thêm quét dependency vào CI. Với dự án Node dùng npm audit --audit-level=high; dự án đa ngôn ngữ dùng Trivy: trivy fs --severity HIGH,CRITICAL .. Đây là chiến thắng nhanh, ít false positive.
Bước 2 — Thêm SAST ở tầng pull request.
Cài Semgrep với --config=auto. Ban đầu để chế độ cảnh báo, không chặn build (non-blocking), để team làm quen mà không bị chặn công việc. Quan sát 1–2 tuần xem tỷ lệ false positive.
Bước 3 — Tinh chỉnh ruleset. Loại bỏ rule gây nhiễu, thêm rule tùy chỉnh cho pattern nguy hiểm đặc thù dự án bạn (ví dụ: cấm gọi trực tiếp API MoMo mà không verify chữ ký). Đặt baseline: chỉ chặn issue mới phát sinh trong diff, không đòi trả sạch nợ cũ ngay.
Bước 4 — Dựng DAST trên staging. Bắt đầu bằng ZAP baseline scan chạy sau mỗi lần deploy staging. Đây là quét thụ động, nhanh, an toàn — không phá dữ liệu.
Bước 5 — Cấu hình authentication cho DAST. Nạp session token / cookie của tài khoản test để ZAP quét được vùng cần đăng nhập. Đây là bước nâng độ phủ lên gấp đôi.
Bước 6 — Chuyển scan nặng sang lịch nightly. ZAP full scan (chủ động tấn công) chạy chậm và có thể sinh dữ liệu rác, nên đừng chạy trong pipeline chặn merge. Đặt nó chạy ban đêm trên môi trường staging riêng, gửi báo cáo về Slack/Telegram sáng hôm sau.
Bước 7 — Thiết lập cổng chặn (quality gate) và quy trình xử lý. Quy ước rõ: lỗ hổng Critical/High chặn release, Medium tạo ticket xử lý trong sprint, Low ghi nhận theo dõi. Không có quy trình xử lý thì báo cáo chỉ để cho đẹp.
Ví dụ một job trong GitHub Actions gộp SCA + SAST:
name: security-scan
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: SCA - Trivy
run: trivy fs --severity HIGH,CRITICAL --exit-code 1 .
- name: SAST - Semgrep
run: semgrep --config=auto --error .
Lỗi thường gặp & mẹo
Lỗi 1 — Bật full scan chặn merge ngay từ đầu. Full scan có thể mất 30–60 phút và làm nghẽn pipeline, khiến cả team ghét security. Mẹo: baseline scan cho pipeline, full scan cho nightly.
Lỗi 2 — Chạy DAST trên production. Full scan gửi payload tấn công thật, có thể tạo hàng nghìn record rác, gửi email spam thật cho khách, thậm chí xóa dữ liệu. Luôn quét trên staging với dữ liệu giả. Nếu bắt buộc quét prod, dùng chế độ thụ động và loại trừ các endpoint gây tác dụng phụ.
Lỗi 3 — Bỏ qua cấu hình authentication cho DAST. Không đăng nhập = chỉ quét được vỏ ngoài. Đây là nguyên nhân số một khiến DAST bỏ sót lỗ hổng nghiêm trọng.
Lỗi 4 — Coi mọi cảnh báo là như nhau. Học cách đọc severity và CVSS score. Ưu tiên xử lý theo mức độ ảnh hưởng thực tế trên hệ thống của bạn, đừng chạy theo số lượng.
Lỗi 5 — Không có bước triage (phân loại). Cần một người/quy trình lọc false positive trước khi đổ hết cho dev. Nếu không, dev sẽ mất niềm tin và bỏ qua tất cả.
Mẹo quản lý ngoại lệ: Với false positive đã xác nhận, dùng cơ chế "baseline" hoặc "suppress" của công cụ (ví dụ file .semgrepignore, hay đánh dấu "won't fix" trong SonarQube) — ghi rõ lý do — thay vì tắt cả rule. Như vậy rule vẫn bắt được ca mới trong tương lai.
Mẹo về diacritic: Khi test input tiếng Việt, nhớ rằng payload tấn công có thể ẩn trong chuỗi Unicode có dấu; đảm bảo công cụ và ứng dụng xử lý UTF-8 nhất quán để tránh cả false positive lẫn bỏ sót.
Bài tập thực hành
- SAST cơ bản: Chọn một project mã nguồn mở nhỏ trên GitHub (Python hoặc JS). Chạy
semgrep --config=auto .và đọc báo cáo. Chọn ra 3 cảnh báo, tự phân loại đâu là true positive, đâu là false positive, và giải thích lý do.
- Viết rule tùy chỉnh: Viết một rule Semgrep bằng YAML để phát hiện việc hardcode một API key (ví dụ chuỗi bắt đầu bằng
sk_live_). Test rule đó trên một file mẫu bạn tự tạo.
- DAST với ZAP: Dùng ứng dụng cố tình có lỗ hổng OWASP Juice Shop (chạy bằng Docker:
docker run -p 3000:3000 bkimminich/juice-shop). Chạyzap-baseline.pynhắm vàohttp://localhost:3000và liệt kê 5 loại lỗ hổng ZAP tìm được.
- So sánh phủ: Trên cùng Juice Shop, chạy cả baseline scan (không auth) và full scan. So sánh số lượng và loại lỗ hổng tìm được giữa hai lần. Rút ra kết luận về giá trị của authentication + active scan.
- Thiết kế cổng chặn: Viết một đoạn cấu hình CI (GitHub Actions hoặc GitLab CI) chạy Trivy + Semgrep, chỉ fail build khi có lỗ hổng mức HIGH trở lên, và cho phép Medium/Low đi qua nhưng vẫn in cảnh báo.
Tóm tắt
Security testing automation là kỹ năng đưa bảo mật vào ngay trong quy trình test hàng ngày, thay vì để dồn tới cuối kỳ. Hai trụ cột là SAST — quét mã nguồn tĩnh, chạy sớm, bắt code pattern nguy hiểm nhưng nhiều false positive; và DAST — quét ứng dụng đang chạy, chạy trên staging, bắt lỗ hổng khai thác thực tế nhưng chỉ phủ được phần code được chạm tới. Hai công cụ này bổ trợ nhau, và nên đi kèm SCA để quét thư viện bên thứ ba — nơi phần lớn rủi ro thực sự nằm.
Ba nguyên tắc cốt lõi cần khắc cốt: bắt đầu từ chiến thắng dễ (SCA), luôn tinh chỉnh để tránh alert fatigue, và không bao giờ có báo cáo mà thiếu quy trình xử lý. Bố trí đúng: baseline/SAST/SCA chặn ở PR, full scan chạy nightly, mọi thứ đều trên staging. Làm được vậy, bạn không chỉ là QA chức năng — bạn đã trở thành một SDET biết bảo vệ sản phẩm khỏi những lỗ hổng mà test truyền thống không bao giờ thấy.