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:

OccurrenceBan Duration
1st ban4 hours
2nd ban24 hours
3rd ban and beyond168 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:

FieldValue
Source IPAzure France
Hits detected23 in < 1 minute
Targeted URIs/wp.php, /shell.php, /gpt-sh.php, /wp-content/plugins/hellopress/wp_filemanager.phpโ€ฆ
Cloudflare actionmanaged_challenge (Bot Fight Mode)
Ban applied168h (recidivist)
AbuseIPDB score100%
Detection โ†’ ban delay< 5 minutes

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

Files and Services

ComponentLocation
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 servicecrowdsec-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