mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
V2
This commit is contained in:
@@ -11,69 +11,513 @@ metadata:
|
||||
|
||||
Protect web applications with Web Application Firewalls.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Deploying a public-facing web application that needs attack protection
|
||||
- Meeting compliance requirements (PCI-DSS, SOC2) for web application security
|
||||
- Blocking OWASP Top 10 attack categories (SQLi, XSS, CSRF, etc.)
|
||||
- Protecting APIs from abuse, injection, and rate-based attacks
|
||||
- Adding a virtual patching layer while application code is being fixed
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Web application behind a load balancer or reverse proxy
|
||||
- AWS account for AWS WAF, or Cloudflare account for Cloudflare WAF
|
||||
- Nginx with ModSecurity module compiled for self-hosted WAF
|
||||
- Access to application logs to tune rules and identify false positives
|
||||
- Understanding of HTTP request/response structure
|
||||
|
||||
## AWS WAF
|
||||
|
||||
### Create Web ACL with Managed Rules
|
||||
|
||||
```bash
|
||||
# Create Web ACL
|
||||
# Create Web ACL with AWS managed rules
|
||||
aws wafv2 create-web-acl \
|
||||
--name my-waf \
|
||||
--name production-waf \
|
||||
--scope REGIONAL \
|
||||
--default-action Allow={} \
|
||||
--rules file://rules.json
|
||||
|
||||
# Associate with ALB
|
||||
aws wafv2 associate-web-acl \
|
||||
--web-acl-arn arn:aws:wafv2:... \
|
||||
--resource-arn arn:aws:elasticloadbalancing:...
|
||||
--visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=production-waf \
|
||||
--rules file://waf-rules.json
|
||||
```
|
||||
|
||||
## ModSecurity (nginx)
|
||||
### AWS WAF Rules Configuration
|
||||
|
||||
```nginx
|
||||
# nginx.conf
|
||||
load_module modules/ngx_http_modsecurity_module.so;
|
||||
|
||||
server {
|
||||
modsecurity on;
|
||||
modsecurity_rules_file /etc/nginx/modsec/main.conf;
|
||||
}
|
||||
```json
|
||||
[
|
||||
{
|
||||
"Name": "AWSManagedRulesCommonRuleSet",
|
||||
"Priority": 1,
|
||||
"Statement": {
|
||||
"ManagedRuleGroupStatement": {
|
||||
"VendorName": "AWS",
|
||||
"Name": "AWSManagedRulesCommonRuleSet",
|
||||
"ExcludedRules": []
|
||||
}
|
||||
},
|
||||
"OverrideAction": { "None": {} },
|
||||
"VisibilityConfig": {
|
||||
"SampledRequestsEnabled": true,
|
||||
"CloudWatchMetricsEnabled": true,
|
||||
"MetricName": "AWSCommonRules"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "AWSManagedRulesSQLiRuleSet",
|
||||
"Priority": 2,
|
||||
"Statement": {
|
||||
"ManagedRuleGroupStatement": {
|
||||
"VendorName": "AWS",
|
||||
"Name": "AWSManagedRulesSQLiRuleSet"
|
||||
}
|
||||
},
|
||||
"OverrideAction": { "None": {} },
|
||||
"VisibilityConfig": {
|
||||
"SampledRequestsEnabled": true,
|
||||
"CloudWatchMetricsEnabled": true,
|
||||
"MetricName": "AWSSQLiRules"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "AWSManagedRulesKnownBadInputsRuleSet",
|
||||
"Priority": 3,
|
||||
"Statement": {
|
||||
"ManagedRuleGroupStatement": {
|
||||
"VendorName": "AWS",
|
||||
"Name": "AWSManagedRulesKnownBadInputsRuleSet"
|
||||
}
|
||||
},
|
||||
"OverrideAction": { "None": {} },
|
||||
"VisibilityConfig": {
|
||||
"SampledRequestsEnabled": true,
|
||||
"CloudWatchMetricsEnabled": true,
|
||||
"MetricName": "AWSBadInputRules"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "RateLimitRule",
|
||||
"Priority": 4,
|
||||
"Statement": {
|
||||
"RateBasedStatement": {
|
||||
"Limit": 2000,
|
||||
"AggregateKeyType": "IP"
|
||||
}
|
||||
},
|
||||
"Action": { "Block": {} },
|
||||
"VisibilityConfig": {
|
||||
"SampledRequestsEnabled": true,
|
||||
"CloudWatchMetricsEnabled": true,
|
||||
"MetricName": "RateLimit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "GeoBlockRule",
|
||||
"Priority": 5,
|
||||
"Statement": {
|
||||
"GeoMatchStatement": {
|
||||
"CountryCodes": ["KP", "IR", "SY"]
|
||||
}
|
||||
},
|
||||
"Action": { "Block": {} },
|
||||
"VisibilityConfig": {
|
||||
"SampledRequestsEnabled": true,
|
||||
"CloudWatchMetricsEnabled": true,
|
||||
"MetricName": "GeoBlock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "BlockBadUserAgents",
|
||||
"Priority": 6,
|
||||
"Statement": {
|
||||
"ByteMatchStatement": {
|
||||
"SearchString": "sqlmap",
|
||||
"FieldToMatch": { "SingleHeader": { "Name": "user-agent" } },
|
||||
"TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
|
||||
"PositionalConstraint": "CONTAINS"
|
||||
}
|
||||
},
|
||||
"Action": { "Block": {} },
|
||||
"VisibilityConfig": {
|
||||
"SampledRequestsEnabled": true,
|
||||
"CloudWatchMetricsEnabled": true,
|
||||
"MetricName": "BadUserAgent"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Associate WAF with ALB
|
||||
|
||||
```bash
|
||||
# Install OWASP CRS
|
||||
git clone https://github.com/coreruleset/coreruleset /etc/nginx/modsec/crs
|
||||
# Associate with Application Load Balancer
|
||||
aws wafv2 associate-web-acl \
|
||||
--web-acl-arn arn:aws:wafv2:us-east-1:123456789:regional/webacl/production-waf/abc123 \
|
||||
--resource-arn arn:aws:elasticloadbalancing:us-east-1:123456789:loadbalancer/app/my-alb/abc123
|
||||
|
||||
# Associate with API Gateway
|
||||
aws wafv2 associate-web-acl \
|
||||
--web-acl-arn arn:aws:wafv2:us-east-1:123456789:regional/webacl/production-waf/abc123 \
|
||||
--resource-arn arn:aws:apigateway:us-east-1::/restapis/abc123/stages/prod
|
||||
```
|
||||
|
||||
### AWS WAF Terraform
|
||||
|
||||
```hcl
|
||||
resource "aws_wafv2_web_acl" "main" {
|
||||
name = "production-waf"
|
||||
scope = "REGIONAL"
|
||||
description = "Production WAF with OWASP protections"
|
||||
|
||||
default_action {
|
||||
allow {}
|
||||
}
|
||||
|
||||
rule {
|
||||
name = "AWSManagedRulesCommonRuleSet"
|
||||
priority = 1
|
||||
|
||||
override_action { none {} }
|
||||
|
||||
statement {
|
||||
managed_rule_group_statement {
|
||||
name = "AWSManagedRulesCommonRuleSet"
|
||||
vendor_name = "AWS"
|
||||
|
||||
rule_action_override {
|
||||
name = "SizeRestrictions_BODY"
|
||||
action_to_use { count {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "AWSCommonRules"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
rule {
|
||||
name = "RateLimit"
|
||||
priority = 10
|
||||
|
||||
action { block {} }
|
||||
|
||||
statement {
|
||||
rate_based_statement {
|
||||
limit = 2000
|
||||
aggregate_key_type = "IP"
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "RateLimit"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
visibility_config {
|
||||
cloudwatch_metrics_enabled = true
|
||||
metric_name = "production-waf"
|
||||
sampled_requests_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_wafv2_web_acl_association" "alb" {
|
||||
resource_arn = aws_lb.main.arn
|
||||
web_acl_arn = aws_wafv2_web_acl.main.arn
|
||||
}
|
||||
```
|
||||
|
||||
## Cloudflare WAF
|
||||
|
||||
### API Configuration
|
||||
|
||||
```bash
|
||||
# Enable managed rules via API
|
||||
curl -X PUT "https://api.cloudflare.com/client/v4/zones/{zone}/firewall/waf/packages/{package}/rules/{rule}" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"mode":"block"}'
|
||||
# List available WAF rulesets
|
||||
curl -s "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/rulesets" \
|
||||
-H "Authorization: Bearer ${CF_TOKEN}" | jq '.result[] | {id, name, phase}'
|
||||
|
||||
# Create a custom WAF rule
|
||||
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/rulesets" \
|
||||
-H "Authorization: Bearer ${CF_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Custom WAF Rules",
|
||||
"kind": "zone",
|
||||
"phase": "http_request_firewall_custom",
|
||||
"rules": [
|
||||
{
|
||||
"action": "block",
|
||||
"expression": "(http.request.uri.query contains \"union select\" or http.request.uri.query contains \"1=1\")",
|
||||
"description": "Block SQL injection patterns in query string"
|
||||
},
|
||||
{
|
||||
"action": "block",
|
||||
"expression": "(http.request.uri.path contains \"..%2f\" or http.request.uri.path contains \"..%5c\")",
|
||||
"description": "Block path traversal attempts"
|
||||
},
|
||||
{
|
||||
"action": "challenge",
|
||||
"expression": "(cf.threat_score gt 30)",
|
||||
"description": "Challenge high threat score visitors"
|
||||
},
|
||||
{
|
||||
"action": "block",
|
||||
"expression": "(http.request.headers[\"user-agent\"] contains \"sqlmap\" or http.request.headers[\"user-agent\"] contains \"nikto\")",
|
||||
"description": "Block known attack tools"
|
||||
}
|
||||
]
|
||||
}'
|
||||
|
||||
# Configure rate limiting
|
||||
curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/rulesets" \
|
||||
-H "Authorization: Bearer ${CF_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Rate Limiting",
|
||||
"kind": "zone",
|
||||
"phase": "http_ratelimit",
|
||||
"rules": [
|
||||
{
|
||||
"action": "block",
|
||||
"ratelimit": {
|
||||
"characteristics": ["ip.src"],
|
||||
"period": 60,
|
||||
"requests_per_period": 100,
|
||||
"mitigation_timeout": 600
|
||||
},
|
||||
"expression": "(http.request.uri.path matches \"^/api/\")",
|
||||
"description": "Rate limit API endpoints"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
## Common Rules
|
||||
### Cloudflare Terraform
|
||||
|
||||
```yaml
|
||||
protections:
|
||||
- SQL Injection (SQLi)
|
||||
- Cross-Site Scripting (XSS)
|
||||
- Remote File Inclusion (RFI)
|
||||
- Local File Inclusion (LFI)
|
||||
- Command Injection
|
||||
- Cross-Site Request Forgery (CSRF)
|
||||
```hcl
|
||||
resource "cloudflare_ruleset" "waf_custom" {
|
||||
zone_id = var.zone_id
|
||||
name = "Custom WAF Rules"
|
||||
kind = "zone"
|
||||
phase = "http_request_firewall_custom"
|
||||
|
||||
rules {
|
||||
action = "block"
|
||||
expression = "(http.request.uri.query contains \"union select\")"
|
||||
description = "Block SQL injection in query string"
|
||||
}
|
||||
|
||||
rules {
|
||||
action = "managed_challenge"
|
||||
expression = "(cf.threat_score gt 30)"
|
||||
description = "Challenge suspicious visitors"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ModSecurity with Nginx
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install ModSecurity for Nginx (Ubuntu)
|
||||
apt install -y libmodsecurity3 libmodsecurity-dev nginx libnginx-mod-http-modsecurity
|
||||
|
||||
# Or compile from source
|
||||
git clone https://github.com/SpiderLabs/ModSecurity /opt/modsecurity
|
||||
cd /opt/modsecurity
|
||||
git submodule init && git submodule update
|
||||
./build.sh && ./configure && make && make install
|
||||
```
|
||||
|
||||
### Nginx Configuration
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/nginx.conf
|
||||
load_module modules/ngx_http_modsecurity_module.so;
|
||||
|
||||
http {
|
||||
modsecurity on;
|
||||
modsecurity_rules_file /etc/nginx/modsec/main.conf;
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name example.com;
|
||||
|
||||
# ModSecurity can also be enabled per-location
|
||||
location /api/ {
|
||||
modsecurity on;
|
||||
modsecurity_rules_file /etc/nginx/modsec/api-rules.conf;
|
||||
proxy_pass http://backend;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ModSecurity Main Configuration
|
||||
|
||||
```bash
|
||||
# /etc/nginx/modsec/main.conf
|
||||
Include /etc/nginx/modsec/modsecurity.conf
|
||||
|
||||
# Set to DetectionOnly first, switch to On after tuning
|
||||
SecRuleEngine On
|
||||
|
||||
# Request body handling
|
||||
SecRequestBodyAccess On
|
||||
SecRequestBodyLimit 13107200
|
||||
SecRequestBodyNoFilesLimit 131072
|
||||
|
||||
# Response body handling
|
||||
SecResponseBodyAccess On
|
||||
SecResponseBodyMimeType text/plain text/html text/xml application/json
|
||||
|
||||
# Logging
|
||||
SecAuditEngine RelevantOnly
|
||||
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
|
||||
SecAuditLogParts ABIJDEFHZ
|
||||
SecAuditLogType Serial
|
||||
SecAuditLog /var/log/modsec/modsec_audit.log
|
||||
|
||||
# Include OWASP Core Rule Set
|
||||
Include /etc/nginx/modsec/crs/crs-setup.conf
|
||||
Include /etc/nginx/modsec/crs/rules/*.conf
|
||||
```
|
||||
|
||||
### OWASP Core Rule Set Setup
|
||||
|
||||
```bash
|
||||
# Download and install OWASP CRS
|
||||
cd /etc/nginx/modsec
|
||||
git clone https://github.com/coreruleset/coreruleset crs
|
||||
cp crs/crs-setup.conf.example crs/crs-setup.conf
|
||||
|
||||
# Customize CRS settings
|
||||
cat >> crs/crs-setup.conf << 'EOF'
|
||||
|
||||
# Set paranoia level (1-4, higher = more strict)
|
||||
SecAction "id:900000, phase:1, pass, t:none, nolog, setvar:tx.paranoia_level=2"
|
||||
|
||||
# Set anomaly score thresholds
|
||||
SecAction "id:900110, phase:1, pass, t:none, nolog, \
|
||||
setvar:tx.inbound_anomaly_score_threshold=5, \
|
||||
setvar:tx.outbound_anomaly_score_threshold=4"
|
||||
|
||||
# Exclude known false positives
|
||||
SecRule REQUEST_URI "@beginsWith /api/upload" \
|
||||
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=920420"
|
||||
EOF
|
||||
|
||||
# Create rule exclusions file
|
||||
cat > /etc/nginx/modsec/crs/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf << 'EOF'
|
||||
# Exclude rules that cause false positives on specific paths
|
||||
SecRule REQUEST_URI "@beginsWith /api/webhook" \
|
||||
"id:1000001,phase:1,pass,nolog,ctl:ruleRemoveTargetById=942100;ARGS:payload"
|
||||
|
||||
# Exclude rules for specific parameters
|
||||
SecRule ARGS_NAMES "^content$" \
|
||||
"id:1000002,phase:1,pass,nolog,ctl:ruleRemoveTargetById=941100;ARGS:content"
|
||||
EOF
|
||||
```
|
||||
|
||||
### Custom ModSecurity Rules
|
||||
|
||||
```bash
|
||||
# /etc/nginx/modsec/custom-rules.conf
|
||||
|
||||
# Block requests with known attack tool user agents
|
||||
SecRule REQUEST_HEADERS:User-Agent "@pm sqlmap nikto nmap masscan dirbuster" \
|
||||
"id:10001,phase:1,deny,status:403,log,msg:'Blocked attack tool'"
|
||||
|
||||
# Block requests to sensitive paths
|
||||
SecRule REQUEST_URI "@rx /(\.git|\.env|\.svn|wp-admin|phpmyadmin|adminer)" \
|
||||
"id:10002,phase:1,deny,status:404,log,msg:'Blocked sensitive path access'"
|
||||
|
||||
# Rate limit by IP (10 requests/second)
|
||||
SecRule IP:REQUEST_RATE "@gt 10" \
|
||||
"id:10003,phase:1,deny,status:429,log,msg:'Rate limit exceeded',\
|
||||
setvar:IP.request_rate=+1,expirevar:IP.request_rate=1"
|
||||
|
||||
# Block oversized cookies (potential overflow attack)
|
||||
SecRule REQUEST_HEADERS:Cookie "@gt 4096" \
|
||||
"id:10004,phase:1,deny,status:400,log,msg:'Oversized cookie header'"
|
||||
|
||||
# Virtual patch: block specific CVE exploit pattern
|
||||
SecRule ARGS:filename "@contains ../../" \
|
||||
"id:10005,phase:2,deny,status:403,log,msg:'Path traversal blocked (virtual patch CVE-XXXX-XXXX)'"
|
||||
|
||||
# Require Content-Type on POST requests
|
||||
SecRule REQUEST_METHOD "@streq POST" \
|
||||
"id:10006,phase:1,chain,deny,status:400,log,msg:'POST without Content-Type'"
|
||||
SecRule &REQUEST_HEADERS:Content-Type "@eq 0" ""
|
||||
```
|
||||
|
||||
## WAF Tuning Workflow
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# waf-tune.sh - Analyze WAF logs for false positives
|
||||
|
||||
AUDIT_LOG="/var/log/modsec/modsec_audit.log"
|
||||
TIMEFRAME="24h"
|
||||
|
||||
echo "=== WAF Tuning Report ==="
|
||||
echo "Analyzing last ${TIMEFRAME} of audit logs"
|
||||
echo ""
|
||||
|
||||
# Top blocked rules
|
||||
echo "--- Top 10 triggered rules ---"
|
||||
grep -oP 'id "\K[0-9]+' "$AUDIT_LOG" | sort | uniq -c | sort -rn | head -10
|
||||
|
||||
echo ""
|
||||
echo "--- Top blocked URIs ---"
|
||||
grep -oP 'REQUEST_URI: \K[^\s]+' "$AUDIT_LOG" | sort | uniq -c | sort -rn | head -10
|
||||
|
||||
echo ""
|
||||
echo "--- Top blocked IPs ---"
|
||||
grep -oP 'client \K[0-9.]+' "$AUDIT_LOG" | sort | uniq -c | sort -rn | head -10
|
||||
|
||||
echo ""
|
||||
echo "--- False positive candidates (high-frequency blocks on common paths) ---"
|
||||
grep -oP 'id "\K[0-9]+' "$AUDIT_LOG" | sort | uniq -c | sort -rn | \
|
||||
while read count rule_id; do
|
||||
if [ "$count" -gt 100 ]; then
|
||||
echo " Rule $rule_id triggered $count times - review for false positive"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Cause | Solution |
|
||||
|---------|-------|----------|
|
||||
| Legitimate requests blocked | False positives from CRS rules | Set `SecRuleEngine DetectionOnly` first; review audit log; add exclusions |
|
||||
| WAF not blocking attacks | Rules in detection-only mode | Switch `SecRuleEngine On` after tuning period |
|
||||
| High latency with WAF enabled | Response body inspection overhead | Disable `SecResponseBodyAccess` if not needed; reduce `paranoia_level` |
|
||||
| AWS WAF rules not matching | Rule priority order wrong | Lower priority number = evaluated first; reorder rules |
|
||||
| ModSecurity crashes nginx | Memory exhaustion on large requests | Increase `SecRequestBodyLimit`; adjust `SecPcreMatchLimit` |
|
||||
| Cloudflare WAF blocks API calls | Expression too broad | Narrow expression with path or method conditions |
|
||||
| CRS update breaks application | New rules trigger on existing traffic | Pin CRS version; test updates in staging first |
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Start in detection mode
|
||||
- Tune for false positives
|
||||
- Monitor blocked requests
|
||||
- Regular rule updates
|
||||
- Custom rules for app-specific attacks
|
||||
- Start in detection/log mode, switch to blocking after tuning
|
||||
- Tune rules for at least 1-2 weeks before enforcement
|
||||
- Monitor blocked requests daily during tuning phase
|
||||
- Update managed rule sets and CRS regularly
|
||||
- Create custom rules for application-specific attack patterns
|
||||
- Use virtual patching to protect against known CVEs while code is being fixed
|
||||
- Set appropriate rate limits per endpoint
|
||||
- Maintain exclusion rules documentation with justifications
|
||||
- Test WAF rules with known attack payloads before deploying
|
||||
- Keep audit logs for at least 90 days for forensic analysis
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [dast-scanning](../../scanning/dast-scanning/) - Web security testing
|
||||
- [ssl-tls-management](../ssl-tls-management/) - HTTPS configuration
|
||||
- [firewall-config](../firewall-config/) - Network-level firewalling
|
||||
|
||||
Reference in New Issue
Block a user