Contents

Automating IP Bans with Cloudflare WAF, CrowdSec and AbuseIPDB

โšก In short

Passive monitoring is not enough. This pipeline automates closing the loop in under 5 minutes between an attack detected by Cloudflare WAF and the effective ban of the IP in CrowdSec, its synchronization to Cloudflare, and its report to AbuseIPDB. A Python script polls the Cloudflare GraphQL API every 5 minutes, applies a 3-hit threshold, and triggers the ban with recidivist escalation.

๐Ÿง  Why

Seeing an attack in BetterStack logs after the fact does not stop the malicious IP from continuing to hammer the server. Without automation, the detection โ†’ ban loop takes hours or never closes. Cloudflare WAF actions (block, challenge, managed_challenge, jschallenge) are clear attack signals, but they remain confined to the Cloudflare dashboard โ€” without a bridge to CrowdSec, no IP is banned locally, none is reported to the AbuseIPDB community.

๐Ÿ”ง What was done

Pipeline Architecture

Cloudflare WAF (block / challenge / managed_challenge)
        โ†“  poll every 5 minutes (GraphQL API)
crowdsec-cf-sync.py  โ†’  threshold: 3 hits / 5 min per IP
        โ†“
CrowdSec (cscli decisions add)  โ†’  recidivist escalation
        โ†“
Cloudflare IP Access Rules (synchronized ban)
        โ†“
AbuseIPDB (report categories 19 + 21)
        โ†“
BetterStack (enriched log in crowdsec-decisions source)

1. Cloudflare WAF Event Collection

The poll_cloudflare_waf() function queries the Cloudflare GraphQL API every 5 minutes and retrieves events with the following actions:

  • block
  • challenge
  • managed_challenge
  • jschallenge

A state file (cf_waf_state.json) stores the timestamp of the last processed event to avoid duplicates between cycles.

2. Detection and Threshold

Events are grouped by source IP. A ban is triggered only if an IP accumulates 3 or more hits within a 5-minute sliding window. This threshold avoids false positives from legitimate challenges (monitoring bots, declared scanners such as Palo Alto Xpanse, etc.).

3. Allowlist

The existing CrowdSec allowlist (my_allowlist) is checked before any ban. It includes monitoring bot IPs, Cloudflare and BetterStack ranges. No allowlisted IP can be banned by this mechanism.

4. Ban Escalation (Recidivists)

The system applies the same escalation logic as other ban sources:

Occurrence Ban Duration
1st ban 4 hours
2nd ban 24 hours
3rd ban and beyond 168 hours (7 days)

The recidivism counter is shared with classic CrowdSec bans via recidivists.json.

5. AbuseIPDB Reporting

Each banned IP is automatically reported to AbuseIPDB with:

  • Category 19: Web Application Attack
  • Category 21: Web App Scan
  • A comment including the Cloudflare hit count and targeted URIs

6. BetterStack Logging

Each ban generates an enriched entry in the crowdsec-decisions source with specific fields:

{
  "source": "cloudflare_waf",
  "cf_action": "managed_challenge",
  "hit_count": 23,
  "uris_targeted": ["/wp.php", "/shell.php", "/backdoor.php"],
  "cs_scenario": "cloudflare-waf/23-hits",
  "cs_duration": "168h"
}

Real Example โ€” Night of April 9, 2026

A massive PHP webshell scan was detected and automatically neutralized:

Field Value
Source IP Azure France
Hits detected 23 in < 1 minute
Targeted URIs /wp.php, /shell.php, /gpt-sh.php, /wp-content/plugins/hellopress/wp_filemanager.phpโ€ฆ
Cloudflare action managed_challenge (Bot Fight Mode)
Ban applied 168h (recidivist)
AbuseIPDB score 100%
Detection โ†’ ban delay < 5 minutes

None of these resources exist on the server (Grav CMS, not WordPress).

Files and Services

Component Location
Main script /usr/local/bin/crowdsec-cf-sync.py
WAF state /var/log/crowdsec/cf_waf_state.json
Recidivists /var/log/crowdsec/recidivists.json
Systemd service crowdsec-cf-sync.service
Vector config /etc/vector/vector.yaml

Useful Commands

# Monitor WAF bans in real time
journalctl -fu crowdsec-cf-sync | grep -i "waf\|cloudflare"

# View recidivists
cat /var/log/crowdsec/recidivists.json | python3 -c "
import json,sys
d=json.load(sys.stdin)
for ip,v in sorted(d.items(), key=lambda x: x[1]['count'], reverse=True):
    print(f'{ip:20} count={v[\"count\"]} last={v[\"last_seen\"]}')
"

# View last WAF poll state
cat /var/log/crowdsec/cf_waf_state.json | python3 -m json.tool

# Restart the service
systemctl restart crowdsec-cf-sync

๐Ÿ Conclusion

This pipeline reduces the response time to an attack from hours to under 5 minutes. Aggressive IPs are banned locally, synchronized to Cloudflare, and reported to the AbuseIPDB community automatically without manual intervention. Recidivist escalation ensures persistent attackers accumulate increasingly long bans.

To go further:

  • ๐Ÿ’ก Add VirusTotal integration to score IPs before reporting to AbuseIPDB
  • ๐Ÿ’ก Implement a dedicated BetterStack dashboard with alerts on WAF ban spikes