ModSecurity Web Application Firewall
ModSecurity เป็น Open-source WAF ที่นิยมที่สุด ทำงานเป็น Module ของ Web Server ตรวจจับ Web Attacks ด้วย Rules วิเคราะห์ทุก HTTP Request/Response ป้องกัน SQL Injection, XSS, RCE และ OWASP Top 10
Best Practices สำหรับ ModSecurity ช่วยให้ WAF ทำงานได้ประสิทธิภาพ ลด False Positive ไม่กระทบ Performance ของเว็บ
ModSecurity Installation และ Configuration
# === ModSecurity Installation ===
# 1. ติดตั้ง ModSecurity กับ Nginx
# Ubuntu/Debian
sudo apt update
sudo apt install -y libmodsecurity3 libmodsecurity-dev
sudo apt install -y nginx libnginx-mod-http-modsecurity
# 2. ติดตั้ง OWASP CRS
cd /etc/nginx
sudo git clone https://github.com/coreruleset/coreruleset.git /etc/nginx/owasp-crs
cd /etc/nginx/owasp-crs
sudo cp crs-setup.conf.example crs-setup.conf
# 3. ModSecurity Configuration
# /etc/nginx/modsecurity/modsecurity.conf
# === Main Configuration ===
# SecRuleEngine DetectionOnly
# เริ่มจาก DetectionOnly ก่อน แล้วค่อยเปลี่ยนเป็น On
SecRuleEngine On
# Request Body
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
SecRequestBodyLimitAction Reject
# Response Body
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml application/json
SecResponseBodyLimit 524288
# Temp Files
SecTmpDir /tmp/modsecurity/tmp/
SecDataDir /tmp/modsecurity/data/
# Audit Log
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
SecAuditLogParts ABCDEFHZ
SecAuditLogType Serial
SecAuditLog /var/log/nginx/modsec_audit.log
# Debug Log (ปิดใน Production)
SecDebugLog /var/log/nginx/modsec_debug.log
SecDebugLogLevel 0
# === Performance Tuning ===
SecPcreMatchLimit 500000
SecPcreMatchLimitRecursion 500000
# 4. Nginx Configuration
# /etc/nginx/conf.d/modsecurity.conf
# server {
# listen 443 ssl http2;
# server_name example.com;
#
# modsecurity on;
# modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
#
# # Include OWASP CRS
# modsecurity_rules_file /etc/nginx/owasp-crs/crs-setup.conf;
# modsecurity_rules_file /etc/nginx/owasp-crs/rules/*.conf;
#
# location / {
# proxy_pass http://backend;
# }
# }
# 5. ทดสอบ
sudo nginx -t
sudo systemctl restart nginx
# ทดสอบ SQL Injection
# curl "https://example.com/?id=1' OR '1'='1"
# ต้องถูก Block (403)
echo "ModSecurity installed with OWASP CRS"
echo " Mode: On (Blocking)"
echo " Rules: OWASP CRS v4"
echo " Audit Log: /var/log/nginx/modsec_audit.log"
Custom Rules และ Exclusions
# === Custom ModSecurity Rules ===
# /etc/nginx/modsecurity/custom-rules.conf
# === 1. Rate Limiting ===
# จำกัด Login Attempts 5 ครั้ง/นาที
# SecAction "id:900001, phase:1, nolog, pass,\
# initcol:ip=%{REMOTE_ADDR}, setvar:ip.login_count=0"
#
# SecRule REQUEST_URI "@streq /api/login" \
# "id:900002, phase:2, deny, status:429,\
# chain, msg:'Login rate limit exceeded'"
# SecRule IP:LOGIN_COUNT "@gt 5" ""
#
# SecRule REQUEST_URI "@streq /api/login" \
# "id:900003, phase:2, pass, nolog,\
# setvar:ip.login_count=+1,\
# expirevar:ip.login_count=60"
# === 2. Block Bad Bots ===
# SecRule REQUEST_HEADERS:User-Agent "@pmFromFile bad-bots.txt" \
# "id:900010, phase:1, deny, status:403,\
# msg:'Bad bot blocked', tag:'bot'"
# === 3. Block Countries (GeoIP) ===
# SecGeoLookupDb /usr/share/GeoIP/GeoLite2-Country.mmdb
# SecRule REMOTE_ADDR "@geoLookup" "chain, id:900020, phase:1, deny, status:403"
# SecRule GEO:COUNTRY_CODE "@pm CN RU KP" "msg:'Blocked country'"
# === 4. API Protection ===
# JSON Body Parsing
# SecRule REQUEST_HEADERS:Content-Type "application/json" \
# "id:900030, phase:1, pass, nolog,\
# ctl:requestBodyProcessor=JSON"
# Block large JSON payloads
# SecRule REQUEST_BODY_LENGTH "@gt 1048576" \
# "id:900031, phase:2, deny, status:413,\
# msg:'JSON body too large'"
# === 5. Exclusion Rules (ลด False Positive) ===
# ยกเว้น WordPress Admin
# SecRule REQUEST_URI "@beginsWith /wp-admin" \
# "id:900100, phase:1, pass, nolog,\
# ctl:ruleRemoveById=941100-941999"
# ยกเว้น Specific Parameter
# SecRule REQUEST_URI "@streq /api/content" \
# "id:900101, phase:1, pass, nolog,\
# ctl:ruleRemoveTargetByTag=OWASP_CRS;ARGS:body"
# ยกเว้น Rule เฉพาะ
# SecRuleRemoveById 920350
# SecRuleRemoveById 942100
# === 6. Virtual Patching ===
# Block specific CVE exploit pattern
# SecRule REQUEST_URI "@rx /vulnerable-endpoint" \
# "id:900200, phase:1, deny, status:403,\
# msg:'Virtual patch for CVE-2024-XXXX',\
# tag:'CVE-2024-XXXX'"
echo "Custom Rules configured"
echo " Rate Limiting: 5 login/min"
echo " Bad Bots: Blocked from file"
echo " API: JSON body parsing"
echo " Exclusions: WordPress admin, specific params"
Monitoring และ Log Analysis
# modsec_monitor.py — ModSecurity Log Analysis
import re
import json
from collections import Counter, defaultdict
from datetime import datetime, timedelta
from dataclasses import dataclass
from typing import List, Dict
@dataclass
class ModSecEvent:
timestamp: str
rule_id: str
severity: str
message: str
uri: str
client_ip: str
action: str
class ModSecAnalyzer:
"""วิเคราะห์ ModSecurity Audit Logs"""
def __init__(self):
self.events: List[ModSecEvent] = []
def parse_audit_log(self, log_path):
"""Parse ModSecurity Audit Log"""
# Simplified parser
current_event = {}
try:
with open(log_path, "r") as f:
for line in f:
line = line.strip()
# Section A: Timestamp + Request
if line.startswith("--") and "-A--" in line:
current_event = {"timestamp": "", "uri": "", "client_ip": ""}
# Section H: Audit Log Trailer (Rule info)
if "Message:" in line:
match = re.search(r'\[id "(\d+)"\]', line)
rule_id = match.group(1) if match else "unknown"
match = re.search(r'\[severity "(\w+)"\]', line)
severity = match.group(1) if match else "NOTICE"
match = re.search(r'\[msg "([^"]+)"\]', line)
message = match.group(1) if match else ""
self.events.append(ModSecEvent(
timestamp=current_event.get("timestamp", ""),
rule_id=rule_id,
severity=severity,
message=message,
uri=current_event.get("uri", ""),
client_ip=current_event.get("client_ip", ""),
action="blocked",
))
except FileNotFoundError:
print(f"Log file not found: {log_path}")
def add_sample_events(self, n=100):
"""เพิ่ม Events ตัวอย่าง"""
import random
rules = [
("941100", "CRITICAL", "XSS Attack Detected"),
("942100", "CRITICAL", "SQL Injection Attack"),
("930110", "CRITICAL", "Path Traversal Attack"),
("932100", "CRITICAL", "Remote Code Execution"),
("920350", "WARNING", "Host header is numeric IP"),
("949110", "CRITICAL", "Inbound Anomaly Score Exceeded"),
]
ips = [f"10.0.{random.randint(1,255)}.{random.randint(1,255)}" for _ in range(20)]
for _ in range(n):
rule = random.choice(rules)
self.events.append(ModSecEvent(
timestamp=datetime.now().isoformat(),
rule_id=rule[0],
severity=rule[1],
message=rule[2],
uri=random.choice(["/login", "/api/users", "/search", "/admin"]),
client_ip=random.choice(ips),
action="blocked",
))
def dashboard(self):
"""แสดง Security Dashboard"""
print(f"\n{'='*60}")
print(f"ModSecurity Dashboard — {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print(f"{'='*60}")
print(f" Total Events: {len(self.events)}")
# By Severity
severity_count = Counter(e.severity for e in self.events)
print(f"\n By Severity:")
for sev, count in severity_count.most_common():
print(f" {sev:>10}: {count}")
# Top Rules
rule_count = Counter(f"{e.rule_id}: {e.message}" for e in self.events)
print(f"\n Top Rules:")
for rule, count in rule_count.most_common(5):
print(f" {count:>5}x {rule}")
# Top IPs
ip_count = Counter(e.client_ip for e in self.events)
print(f"\n Top Attacking IPs:")
for ip, count in ip_count.most_common(5):
print(f" {count:>5}x {ip}")
# Top URIs
uri_count = Counter(e.uri for e in self.events)
print(f"\n Top Targeted URIs:")
for uri, count in uri_count.most_common(5):
print(f" {count:>5}x {uri}")
def false_positive_candidates(self):
"""หา Rules ที่อาจเป็น False Positive"""
rule_uris = defaultdict(set)
for e in self.events:
rule_uris[e.rule_id].add(e.uri)
print(f"\n Potential False Positives (rules hitting many URIs):")
for rule_id, uris in rule_uris.items():
if len(uris) > 3:
print(f" Rule {rule_id}: {len(uris)} URIs — Review needed")
# analyzer = ModSecAnalyzer()
# analyzer.parse_audit_log("/var/log/nginx/modsec_audit.log")
analyzer = ModSecAnalyzer()
analyzer.add_sample_events(200)
analyzer.dashboard()
analyzer.false_positive_candidates()
Best Practices
- DetectionOnly ก่อน: เริ่มจาก DetectionOnly Mode ดู Logs 1-2 สัปดาห์ ก่อนเปลี่ยนเป็น Blocking
- Paranoia Level 1: เริ่มจาก PL1 ค่อยเพิ่มทีละระดับ PL4 เข้มงวดมากจะมี False Positive เยอะ
- Exclusion Rules: สร้าง Exclusion ให้เฉพาะเจาะจง ใช้ URI + Parameter ไม่ใช่ปิดทั้ง Rule
- Log Analysis: Review Audit Logs สม่ำเสมอ หา Attack Patterns และ False Positives
- Update CRS: อัพเดท OWASP CRS เป็นประจำ มี Rules ใหม่สำหรับ Attacks ใหม่
- Virtual Patching: ใช้ ModSecurity Rules แก้ไข Vulnerability ชั่วคราวก่อน Patch Code
ModSecurity คืออะไร
Open-source WAF ทำงานเป็น Module ของ Apache Nginx IIS ตรวจจับป้องกัน SQL Injection XSS Path Traversal File Inclusion ใช้ Rule-based Detection วิเคราะห์ HTTP Request/Response
OWASP CRS คืออะไร
ชุด Rules มาตรฐานสำหรับ ModSecurity พัฒนาโดย OWASP ป้องกัน Top 10 Attacks SQL Injection XSS RCE LFI/RFI Paranoia Levels 1-4 อัพเดทสม่ำเสมอ ฟรี
ModSecurity กับ Cloud WAF ต่างกันอย่างไร
ModSecurity Open-source ติดตั้งเอง ฟรี Customize Rules เต็มที่ ดูแลเอง Cloud WAF Managed Service ไม่ดูแล Server Global Network มีค่าใช้จ่าย Customization จำกัดกว่า
วิธี Tune ModSecurity ลด False Positive ทำอย่างไร
เริ่ม DetectionOnly ดู Logs ก่อน Block Paranoia Level 1 ก่อน Exclusion Rules สำหรับ URI/Parameter SecRuleRemoveById ปิด Rule เฉพาะ ทดสอบ Traffic จริง Review Logs สม่ำเสมอ
สรุป
ModSecurity เป็น WAF ที่ทรงพลังและฟรี ใช้ OWASP CRS ป้องกัน Web Attacks เริ่มจาก DetectionOnly ก่อน Paranoia Level 1 สร้าง Exclusion Rules ลด False Positive Review Audit Logs อัพเดท CRS Virtual Patching แก้ Vulnerability ชั่วคราว
