Tháng trước mình vừa xử lý xong một vụ khá căng: website thương mại điện tử của khách hàng bị kẻ tấn công dump toàn bộ database qua lỗ hổng SQL injection. Server Nginx chạy ngon, firewall UFW cũng bật đầy đủ — vậy mà vẫn bị. Lý do đơn giản đến mức bực bội: cả hai thứ đó đều không “hiểu” được HTTP request đang chứa payload độc hại bên trong.
Vấn đề thực tế: Tại sao website vẫn bị tấn công dù có firewall?
Sau khi audit security cho 10+ server, mình nhận thấy hầu hết đều mắc chung một lỗ hổng: UFW bật, fail2ban chạy, SSH port đổi — nhưng không có thứ gì kiểm soát nội dung HTTP. Đó là điểm mù nguy hiểm nhất mà ít người để ý.
Hãy nghĩ thế này: firewall mạng giống như bảo vệ cổng tòa nhà — họ chỉ kiểm tra xem bạn có thẻ ra vào hợp lệ không (đúng IP, đúng port). Nhưng họ không mở túi xách ra kiểm tra bạn mang gì vào bên trong. Kẻ tấn công hoàn toàn có thể đi vào bằng port 80/443 hợp lệ, rồi nhét payload '; DROP TABLE users; -- vào tham số URL.
Các dạng tấn công phổ biến mà firewall mạng hoàn toàn bất lực:
- SQL Injection — chèn code SQL vào form, URL để truy cập hoặc xóa database
- Cross-Site Scripting (XSS) — chèn script độc vào trang để đánh cắp cookie người dùng
- Path Traversal — đọc file hệ thống ngoài thư mục web (
../../etc/passwd) - Remote File Inclusion (RFI) — load và thực thi file từ server bên ngoài
Phân tích nguyên nhân: Thiếu tầng kiểm soát ở tầng ứng dụng
Mô hình bảo mật truyền thống chỉ có hai tầng: network firewall và application code. Khi code có lỗ hổng — và hầu như code nào cũng có — không có gì chặn được tấn công trước khi nó chạm đến database.
Web Application Firewall (WAF) lấp đúng khoảng trống đó. WAF ngồi giữa người dùng và ứng dụng, đọc từng HTTP request, so sánh với bộ luật, và chặn những gì trông có vẻ độc hại trước khi request đến được code của bạn.
Điều thực dụng nhất: WAF không yêu cầu vá code ngay. Dù ứng dụng còn lỗ hổng SQL injection, WAF vẫn chặn payload trước khi nó gây hại — mua thêm thời gian để fix đúng cách thay vì vá vội trong đêm.
Các cách giải quyết: Chọn WAF nào?
Thực tế mình đã thử cả ba hướng, và đây là nhận xét thẳng thắn:
1. Cloud WAF (Cloudflare, AWS WAF, Sucuri) — dễ bật, không cần cấu hình server, nhưng tốn tiền ($20–$200/tháng tùy gói), dữ liệu request đi qua bên thứ ba, và bạn phụ thuộc vào nhà cung cấp.
2. Commercial WAF hardware/software — F5, Imperva, Fortiweb. Mạnh nhưng giá enterprise, không phù hợp VPS cá nhân hay startup nhỏ.
3. Self-hosted ModSecurity + OWASP CRS — miễn phí hoàn toàn, chạy trực tiếp trên server Nginx, toàn quyền kiểm soát. Đây là thứ mình dùng cho hầu hết server sau khi thử cả ba hướng trên.
ModSecurity là WAF engine mã nguồn mở, chạy như module bên trong Nginx. Còn OWASP Core Rule Set (CRS) là bộ luật do cộng đồng bảo mật toàn cầu duy trì — hiện có hơn 10.000 rule, cập nhật liên tục theo các kỹ thuật bypass mới nhất.
Cài đặt ModSecurity + OWASP CRS trên Nginx (Ubuntu 22.04)
Bước 1: Cài đặt ModSecurity và module Nginx
sudo apt update
sudo apt install -y nginx libnginx-mod-security2 git
Sau khi cài xong, kích hoạt module trong Nginx:
# Kiểm tra module đã có chưa
ls /etc/nginx/modules-enabled/ | grep security
# Nếu chưa có symbolic link, tạo thủ công
sudo ln -s /usr/share/nginx/modules-available/mod-http-modsecurity.conf \
/etc/nginx/modules-enabled/50-mod-http-modsecurity.conf
Bước 2: Cấu hình ModSecurity cơ bản
# Copy file cấu hình mẫu
sudo cp /etc/nginx/modsecurity.conf-recommended /etc/nginx/modsecurity.conf
# Bật chế độ chặn (mặc định là DetectionOnly — chỉ log, không chặn)
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsecurity.conf
Lưu ý: Lần đầu triển khai, mình khuyên để DetectionOnly một tuần để xem có false positive không, rồi mới chuyển sang On. Tắt bật theo cách này:
# Chế độ chỉ log (an toàn khi mới cài)
SecRuleEngine DetectionOnly
# Chế độ chặn thực sự
SecRuleEngine On
Bước 3: Tải và cấu hình OWASP CRS
# Tải OWASP CRS phiên bản mới nhất
cd /etc/nginx
sudo git clone https://github.com/coreruleset/coreruleset.git modsecurity-crs
# Tạo file cấu hình từ mẫu
sudo cp /etc/nginx/modsecurity-crs/crs-setup.conf.example \
/etc/nginx/modsecurity-crs/crs-setup.conf
# Tạo file include tổng hợp
sudo mkdir -p /etc/nginx/modsec
sudo tee /etc/nginx/modsec/main.conf <<'EOF'
Include /etc/nginx/modsecurity.conf
Include /etc/nginx/modsecurity-crs/crs-setup.conf
Include /etc/nginx/modsecurity-crs/rules/*.conf
EOF
Bước 4: Kích hoạt WAF trong cấu hình Nginx
Thêm vào từng server block cần bảo vệ trong /etc/nginx/sites-available/your-site.conf:
server {
listen 80;
server_name example.com;
# Bật ModSecurity cho server block này
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Kiểm tra config trước khi reload
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Kiểm tra WAF hoạt động đúng không
Sau khi cài xong, test ngay bằng cách gửi các payload kinh điển:
# Test SQL Injection
curl -v "http://your-server/?id=1'%20OR%20'1'='1"
# Kỳ vọng: HTTP 403 Forbidden
# Test XSS
curl -v "http://your-server/?q=<script>alert(1)</script>"
# Kỳ vọng: HTTP 403 Forbidden
# Test Path Traversal
curl -v "http://your-server/?file=../../etc/passwd"
# Kỳ vọng: HTTP 403 Forbidden
Xem log để theo dõi request bị chặn:
# Log của ModSecurity nằm trong error log của Nginx
sudo tail -f /var/log/nginx/error.log | grep -i modsecurity
# Hoặc xem audit log riêng
sudo tail -f /var/log/modsec_audit.log
Xử lý false positive: Khi WAF chặn nhầm
False positive là phần gây đau đầu nhất khi mới triển khai. Mình từng gặp trường hợp một form WordPress editor bị chặn vì nội dung bài viết chứa đoạn code HTML — CRS nhận nhầm là XSS.
Cách xử lý: tắt rule cụ thể gây false positive, không tắt cả bộ CRS. Thêm vào /etc/nginx/modsec/main.conf trước dòng include rules:
# Tắt rule cụ thể theo ID (xem ID trong error log)
SecRuleRemoveById 941100 941110
# Hoặc whitelist path cụ thể
SecRule REQUEST_URI "@beginsWith /wp-admin/post.php" \
"id:1000,phase:1,pass,nolog,ctl:ruleRemoveById=941100"
Quy trình mình hay làm: chạy DetectionOnly một tuần, export log, lọc các rule ID hay bắn nhất trên traffic thật của site, rồi đánh giá từng cái xem có thật sự là tấn công không. Với site không có form editor phức tạp, tỉ lệ false positive thường dưới 1%.
Mấy điểm cần nhớ khi vận hành
- Cập nhật CRS định kỳ — OWASP release bộ rule mới thường xuyên để vá các bypass mới. Dùng
git pulltrong thư mục/etc/nginx/modsecurity-crsrồi reload Nginx. - Monitor performance — ModSecurity tốn CPU vì phải parse từng request. Trên server nhỏ (<2 vCPU), theo dõi load sau khi bật.
- Kết hợp với fail2ban — parse log ModSecurity để ban IP khi tấn công liên tục, thay vì chỉ trả về 403 mỗi lần.
- Backup config trước khi thay đổi — một lần mình sửa nhầm main.conf khiến toàn bộ site 403, mất 20 phút restore lại từ backup.
ModSecurity + OWASP CRS không phải viên đạn bạc — vẫn cần vá code, cập nhật dependencies, và audit định kỳ. Nhưng với chi phí bằng 0 và vài tiếng cài đặt, bạn có ngay một lớp phòng thủ thực sự trước khi kịp fix từng dòng code trong ứng dụng. Với mình, đó là đủ lý do để cài lên mọi server.
