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:
blockchallengemanaged_challengejschallenge
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