Preconfigured WAF Rules: OWASP ModSecurity CRS
Why this matters in production
Preconfigured WAF rules là thứ mọi người bật đầu tiên và hiểu sai nhiều nhất. Kỳ vọng phổ biến: "bật OWASP ruleset lên là chặn được hacker". Thực tế cơ chế: đó là một bộ signature matching dựa trên OWASP ModSecurity Core Rule Set (CRS), và nó hoạt động theo nguyên lý phát hiện pattern khả nghi, không phải hiểu ngữ cảnh ứng dụng. Điều này dẫn tới hai vấn đề đối xứng mà bạn bắt buộc phải quản lý:
- False positive: traffic hợp lệ khớp signature (ví dụ một comment chứa chuỗi
' OR '1'='1trong một forum về bảo mật, một câu SQL trong tài liệu, một payload JSON có ký tự đặc biệt). WAF chặn nhầm → user thật bị lỗi 403. - False negative: attack được obfuscate/encode vượt qua signature. WAF không bắt được.
Không hiểu cơ chế signature matching và sensitivity level, bạn sẽ tuning bằng thử-sai, hoặc tệ hơn — tắt WAF vì "nó chặn nhầm nhiều quá". Mục tiêu của file này là cho bạn mental model đủ chính xác để tune một cách có nguyên tắc.
Internal model — cơ chế vận hành
CRS là gì và Cloud Armor dùng nó thế nào
Cloud Armor không tự viết signature từ đầu. Nó đóng gói OWASP ModSecurity Core Rule Set — bộ rule chống tấn công web chuẩn công nghiệp — và biên dịch nó thành các "preconfigured rule set" gọi được qua hàm evaluatePreconfiguredWaf(). Phiên bản hiện tại là CRS 4.22 (*-v422-stable), với các phiên bản cũ hơn (CRS 3.0, 3.3) vẫn tồn tại cho backward compatibility.
Mỗi preconfigured rule set nhắm một lớp tấn công:
| Rule set (CRS 4.22) | Lớp tấn công |
|---|---|
sqli-v422-stable | SQL injection |
xss-v422-stable | Cross-site scripting |
lfi-v422-stable | Local file inclusion |
rfi-v422-stable | Remote file inclusion |
rce-v422-stable | Remote code execution |
methodenforcement-v422-stable | HTTP method enforcement |
scannerdetection-v422-stable | Scanner/tool detection |
protocolattack-v422-stable | Protocol attack (HTTP request smuggling…) |
php-v422-stable, java-v422-stable, nodejs-v422-stable | Tấn công đặc thù theo runtime |
sessionfixation-v422-stable | Session fixation |
cve-canary | Signature cho các CVE cụ thể (ví dụ Log4Shell) |
Mỗi rule set bên trong chứa nhiều signature (mỗi signature có một rule ID như owasp-crs-v042200-id942350-sqli). Khi bạn gọi evaluatePreconfiguredWaf('sqli-v422-stable', ...), Cloud Armor chạy tập signature của rule set đó lên các phần của request (URI, query, header, body — trong giới hạn 8 KB/64 KB đã nói ở file 01). Nếu bất kỳ signature nào khớp (và signature đó đang bật ở sensitivity hiện tại), hàm trả về true, và action của rule (thường là deny) được áp dụng.
Mental model cốt lõi:
evaluatePreconfiguredWaf()là một hàm boolean. Nó không "chặn" — nó chỉ trảtrue/false. Việc chặn là doaction: deny(...)của rule bao quanh nó. Điều này nghĩa là bạn có thể dùng cùng hàm với action khác:allow(để tạo ngoại lệ), hoặc kết hợp trong một expression phức tạp hơn.
Sensitivity level và paranoia level — trục tuning quan trọng nhất
Đây là khái niệm quyết định toàn bộ trade-off false positive/negative. Mỗi signature trong CRS được gán một paranoia level từ 1 đến 4, phản ánh độ tự tin của signature đó:
- Paranoia level 1: signature độ tin cậy cao — pattern gần như chắc chắn là tấn công. Ít false positive.
- Paranoia level 4: signature "hoang tưởng" — bắt cả những pattern có thể là tấn công nhưng cũng thường xuất hiện trong traffic hợp lệ. Nhiều false positive, nhưng bắt được nhiều biến thể hơn.
Trong Cloud Armor, bạn điều khiển điều này qua tham số sensitivity (0–4) khi gọi evaluatePreconfiguredWaf():
evaluatePreconfiguredWaf('sqli-v422-stable', {'sensitivity': 1})Cơ chế: sensitivity: N bật tất cả signature có paranoia level ≤ N. Cụ thể:
sensitivity: 0— tắt toàn bộ signature của rule set. Không bật gì cả. (Dùng cho pattern opt-in, xem dưới.)sensitivity: 1— chỉ bật signature paranoia level 1 (độ tin cậy cao nhất). Ít false positive nhất.sensitivity: 2— bật paranoia level 1 và 2.sensitivity: 3— bật level 1, 2, 3.sensitivity: 4— bật tất cả signature (level 1–4). Nhiều false positive nhất nhưng coverage rộng nhất. Đây là mặc định khi bật rule set qua console.
Theo tài liệu Google: "A lower sensitivity level indicates higher confidence signatures, which are less likely to generate a false positive."
Đây là điểm gây hiểu lầm lớn nhất về WAF. Nhiều người nghĩ "sensitivity cao = an toàn hơn" nên để mặc định 4. Thực tế, sensitivity 4 ở production thường tạo ra cơn lũ false positive chặn user thật, buộc team phải tắt WAF hoàn toàn — kết cục kém an toàn hơn so với chạy sensitivity 1 ổn định. Triết lý đúng: bắt đầu ở sensitivity 1, quan sát, rồi nâng dần nếu cần, mỗi lần nâng đều đi qua preview mode.
Cơ chế tuning tinh: opt-out và opt-in signature
Sensitivity là dao cùn — nó bật/tắt cả nhóm theo paranoia level. Khi một signature cụ thể gây false positive nhưng bạn muốn giữ phần còn lại của rule set, dùng opt-out:
evaluatePreconfiguredWaf('sqli-v422-stable', {
'sensitivity': 4,
'opt_out_rule_ids': [
'owasp-crs-v042200-id942350-sqli',
'owasp-crs-v042200-id942360-sqli'
]
})Điều này chạy toàn bộ sqli ở sensitivity 4 trừ hai signature bị liệt kê. Ngược lại, opt-in dùng khi bạn muốn tắt cả rule set (sensitivity: 0) nhưng bật đúng vài signature:
evaluatePreconfiguredWaf('cve-canary', {
'sensitivity': 0,
'opt_in_rule_ids': ['owasp-crs-v042200-id044228-cve']
})Pattern opt-in đặc biệt hữu ích với cve-canary: bạn có thể chỉ bật signature cho một CVE cụ thể liên quan tới stack của mình (ví dụ Log4Shell) mà không kéo theo mọi thứ khác.
Giới hạn: tối đa 128 rule ID cho mỗi danh sách opt-in/opt-out. Và quan trọng: khi opt-out signature, bạn phải khớp phiên bản signature ID với phiên bản rule set (CRS 3.0 vs 3.3 vs 4.22) — trộn lẫn phiên bản gây lỗi cấu hình.
Request field exclusion — công cụ tuning mạnh nhất
Opt-out tắt một signature trên toàn bộ request. Nhưng thường false positive chỉ đến từ một field cụ thể: ví dụ một field comment trong form cho phép user nhập text tự do (kể cả text trông giống SQL). Bạn không muốn tắt SQLi signature toàn cục, chỉ muốn loại field đó khỏi phạm vi inspect. Đó là request field exclusion.
Bạn có thể loại khỏi WAF inspection:
- Request headers (loại phần value, tên header vẫn được inspect)
- Request cookies (loại value)
- Request query parameters (từ URI hoặc body)
- Request URIs (toàn bộ field)
Với các operator: EQUALS, STARTS_WITH, ENDS_WITH, CONTAINS, EQUALS_ANY.
Ví dụ loại toàn bộ cookie khỏi sqli-v422-stable:
gcloud compute security-policies rules add-preconfig-waf-exclusion PRIORITY \
--security-policy POLICY_1 \
--target-rule-set "sqli-v422-stable" \
--request-cookie-to-exclude "op=EQUALS_ANY"Loại một query param cụ thể:
gcloud compute security-policies rules add-preconfig-waf-exclusion PRIORITY \
--security-policy POLICY_3 \
--target-rule-set "sqli-v422-stable" \
--request-query-param-to-exclude "op=EQUALS,val=bad-param"Giới hạn: 100 exclusion mỗi target mỗi loại field (mặc định cho service-level policy). Và exclusion không dùng được với action allow.
Thứ tự ưu tiên khi tuning (từ tinh tới thô): field exclusion (loại đúng field gây FP) → opt-out signature (tắt đúng signature gây FP) → hạ sensitivity (dao cùn nhất). Luôn ưu tiên biện pháp hẹp nhất có thể — mỗi lần bạn tắt rộng hơn mức cần thiết là bạn mở thêm bề mặt tấn công.
Migration giữa các phiên bản CRS
Cloud Armor giữ nhiều phiên bản CRS song song (*-v33-stable, *-v422-stable, …) vì nâng phiên bản là một thay đổi có rủi ro: signature mới có thể bắt thêm attack nhưng cũng có thể tạo false positive mới trên traffic vốn đang sạch. Cơ chế migration đúng:
- Thêm rule dùng ruleset phiên bản mới (
sqli-v422-stable) ở preview, song song với rule phiên bản cũ đang enforce. - So sánh
previewSecurityPolicy(rule mới) vớienforcedSecurityPolicy(rule cũ) trong log: rule mới bắt thêm gì, chặn nhầm thêm gì. - Chuyển sang phiên bản mới khi đã xác nhận và xử lý các false positive mới bằng exclusion/opt-out.
Lưu ý ràng buộc đã nêu: khi opt-out signature, ID phải khớp phiên bản ruleset. Migrate phiên bản nghĩa là cập nhật lại toàn bộ danh sách opt-out theo ID phiên bản mới — bỏ sót bước này gây lỗi cấu hình hoặc mất các exclusion đang cần.
evaluatePreconfiguredWaf inspect những phần nào của request
Hiểu chính xác bề mặt mà WAF quét giúp reason về cả false positive lẫn false negative. evaluatePreconfiguredWaf() áp signature lên: request URI (path + query), request header (tên và value), cookie, và request body (chỉ hàm này đọc body, trong giới hạn 8 KB/64 KB — xem file 01), với khả năng parse application/x-www-form-urlencoded và JSON. Nó cũng thực hiện các bước normalization nội bộ (URL-decode, lowercase, loại null byte…) trước khi match — nên bạn thường không cần tự decode trong custom rule cho các lớp tấn công chuẩn.
Hệ quả cho tuning: khi một field hợp lệ gây false positive, bạn cần biết field đó thuộc loại nào (header/cookie/query/URI/body) để chọn đúng loại exclusion. matchedFieldType trong verbose log (file 07) cho bạn chính xác thông tin này — đây là lý do verbose logging là công cụ tuning không thể thiếu.
Constraints, trade-offs & failure modes
Signature matching không hiểu ngữ cảnh ứng dụng. WAF không biết field comment của bạn cho phép text tự do; nó chỉ thấy pattern giống SQL. Đây là giới hạn bản chất của signature-based WAF, không phải bug. Mọi công sức tuning là để bù đắp khoảng cách giữa "pattern khả nghi" và "ngữ cảnh thật của app".
False negative qua encoding/obfuscation. Attacker encode payload (URL-encode nhiều lần, base64, comment injection) để tránh signature. Cloud Armor có function decode (.urlDecode(), .base64Decode(), .urlDecodeUni() — xem file 03) nhưng preconfigured signature chỉ decode tới mức nhất định. Đây là lý do WAF là một lớp, không phải lớp duy nhất.
Giới hạn body inspection (8 KB/64 KB) áp dụng trực tiếp: payload SQLi nhét sau 8 KB đầu body có thể không bị evaluatePreconfiguredWaf() bắt.
Chi phí tính toán và độ trễ. Mỗi signature chạy trên mỗi request tốn CPU ở GFE. Sensitivity 4 với nhiều rule set đồng thời tốn hơn sensitivity 1. Ở scale rất lớn, điều này ảnh hưởng tới latency biên — thêm một lý do để không bật bừa sensitivity 4.
Failure mode — rollout WAF không qua preview: bật sqli sensitivity 4 với action deny trực tiếp lên production. Ngay lập tức mọi form có text tự do bắt đầu trả 403 cho user thật. Đây là outage tự gây phổ biến nhất với Cloud Armor. Cách đúng: deploy rule ở preview → quan sát previewSecurityPolicy trong log vài ngày → xác định false positive → thêm exclusion/opt-out → bỏ preview.
Failure mode — tuning quá tay thành vô dụng: vì mệt mỏi với false positive, team opt-out hàng loạt signature hoặc hạ hết về sensitivity 0. WAF vẫn "bật" trên dashboard nhưng thực tế không bắt gì. Đây là security theater. Nếu WAF gây quá nhiều FP tới mức phải vô hiệu hóa nó, vấn đề thường nằm ở chỗ chưa dùng field exclusion đúng chỗ.
Real-world scenario: rollout WAF cho một API nhận JSON
Một API nhận payload JSON lớn (ví dụ analytics events). Bật sqli + xss sensitivity 4 ngay lập tức làm ~3% request hợp lệ bị 403, vì payload JSON chứa chuỗi ngẫu nhiên khớp signature. Quy trình xử lý theo đúng cơ chế:
- Đặt hai rule WAF ở preview, sensitivity 1.
- Quan sát log 3–5 ngày. Phát hiện false positive tập trung ở một field
metadata.raw_query(chứa query string user gửi lên). - Thêm field exclusion cho query param/body field đó thay vì hạ sensitivity toàn cục.
- Với vài signature XSS còn FP lẻ tẻ, dùng opt-out theo rule ID.
- Bỏ preview, enforce ở sensitivity 1. Theo dõi thêm, cân nhắc nâng lên 2 nếu threat model đòi hỏi.
Bài học cơ chế: mỗi bước dùng công cụ hẹp nhất phù hợp, và mỗi thay đổi được kiểm chứng qua log preview trước khi enforce.
Official references
- Tune preconfigured WAF rules — sensitivity, opt-in/opt-out, field exclusion, gcloud examples
- Cloud Armor rules language reference —
evaluatePreconfiguredWaf(), decode functions - OWASP ModSecurity Core Rule Set — nguồn gốc của signature và khái niệm paranoia level
- Cloud Armor overview