Cloudflare Proxy
Cloudflare offers an excellent reverse proxy and they filter most bad actors for you. But not all. Here’s a sample of what makes it through;
allen@www:~/$ sudo cscli alert list
╭─────┬────────────────────┬───────────────────────────────────┬─────────┬────────────────────────┬───────────┬─────────────────────────────────────────╮
│ ID │ value │ reason │ country │ as │ decisions │ created_at │
├─────┼────────────────────┼───────────────────────────────────┼─────────┼────────────────────────┼───────────┼─────────────────────────────────────────┤
│ 221 │ Ip:162.158.49.136 │ crowdsecurity/jira_cve-2021-26086 │ IE │ 13335 CLOUDFLARENET │ ban:1 │ 2025-01-22 15:14:34.554328601 +0000 UTC │
│ 187 │ Ip:128.199.182.152 │ crowdsecurity/jira_cve-2021-26086 │ SG │ 14061 DIGITALOCEAN-ASN │ ban:1 │ 2025-01-19 20:50:45.822199509 +0000 UTC │
│ 186 │ Ip:46.101.1.225 │ crowdsecurity/jira_cve-2021-26086 │ GB │ 14061 DIGITALOCEAN-ASN │ ban:1 │ 2025-01-19 20:50:41.699518104 +0000 UTC │
│ 181 │ Ip:162.158.108.104 │ crowdsecurity/http-bad-user-agent │ SG │ 13335 CLOUDFLARENET │ ban:1 │ 2025-01-19 12:39:20.468268327 +0000 UTC │
│ 180 │ Ip:172.70.208.61 │ crowdsecurity/http-bad-user-agent │ SG │ 13335 CLOUDFLARENET │ ban:1 │ 2025-01-19 12:38:36.664997131 +0000 UTC │
╰─────┴────────────────────┴───────────────────────────────────┴─────────┴────────────────────────┴───────────┴─────────────────────────────────────────╯
You can see that CrowdSec took action, but it was the wrong one. It’s blocking the Cloudflare exit node and removed everyone’s access.
What we want is:
- Identify the actual attacker
- Block that somewhere effective (the firewall-bouncer can’t selectively block proxied traffic)
Identifying The Attacker
We could replace the CrowdSec Caddy log parser and use a different header, but there’s a hint in the CrowdSec parser that suggests using the trusted_proxies
module.
##Caddy now sets client_ip to the value of X-Forwarded-For if users sets trusted proxies
Additionally, we can choose the CF-Connecting-IP
header like francislavoie suggests, as X-Forwarded-For
is easily spoofed.
Add a Trusted Proxy
To set Cloudflare as a trusted proxy we must identify all the Cloudflare exit node IPs to trust them. That would be hard to manage, but happily, there’s a handy caddy-cloudflare-ip module for that. Many thanks to WeidiDeng!
sudo caddy add-package github.com/WeidiDeng/caddy-cloudflare-ip
sudo vi /etc/caddy/Caddyfile
#
# Global Options Block
#
{
servers {
trusted_proxies cloudflare
client_ip_headers CF-Connecting-IP
}
}
After restarting Caddy, we can see the header change
sudo head /var/log/caddy/access.log | jq '.request'
sudo tail /var/log/caddy/access.log | jq '.request'
Before
"remote_ip": "172.68.15.223",
"client_ip": "172.68.15.223",
After
"remote_ip": "172.71.98.114",
"client_ip": "109.206.128.45",
And when consulting crowdsec, we can see it’s using the client_ip
information.
sudo tail /var/log/caddy/access.log > test.log
sudo cscli explain -v --file ./test.log --type caddy
├ s01-parse
| └ 🟢 crowdsecurity/caddy-logs (+14 ~2)
| └ update evt.Stage : s01-parse -> s02-enrich
| └ create evt.Parsed.remote_ip : 109.206.128.45 <-- Your Actual IP
And when launching a probe we can see it show up with the correct IP.
# Ask for lots of pages that don't exist to simulate a HTTP probe
for X in {1..100}; do curl -D - https://www.some.org/$X;done
sudo cscli decisions list
╭─────────┬──────────┬───────────────────┬────────────────────────────┬────────┬─────────┬───────────────┬────────┬────────────┬──────────╮
│ ID │ Source │ Scope:Value │ Reason │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
├─────────┼──────────┼───────────────────┼────────────────────────────┼────────┼─────────┼───────────────┼────────┼────────────┼──────────┤
│ 2040067 │ crowdsec │ Ip:109.206.128.45 │ crowdsecurity/http-probing │ ban │ US │ 600 BADNET-AS │ 11 │ 3h32m5s │ 235 │
╰─────────┴──────────┴───────────────────┴────────────────────────────┴────────┴─────────┴───────────────┴────────┴────────────┴──────────╯
This doesn’t do anything on its own (because traffic is proxied) but we can make it work if we change bouncers.
Changing Bouncers
The ideal approach would to tell Cloudflare to stop forwarding traffic from the bad actors. There is a cloudflare-bouncer
to do just that. It’s rate limited however, and only suitable for premium clients. There is also the CrowdSec Cloudflare Worker. It’s better, but still suffers from limits for non-premium clients.
Caddy Bouncer
Instead, we’ll use the caddy-crowdsec-bouncer. This is a layer 4 (protocol level) bouncer. It works inside Caddy and will block IPs based on the client_ip
from the HTTP request.
Generate an API key for the bouncer with the bouncer add
command - this doesn’t actually install anything, just generates a key.
sudo cscli bouncers add caddy-bouncer
Add the module to Caddy (which is the actual install).
sudo caddy add-package github.com/hslatman/caddy-crowdsec-bouncer
Configure Caddy
#
# Global Options Block
#
{
crowdsec {
api_key ABIGLONGSTRING
}
# Make sure to add the order statement
order crowdsec first
}
www.some.org {
crowdsec
root * /var/www/www.some.org
file_server
}
And restart.
sudo systemctl restart caddy.service
Testing Remediation
Let’s test that probe again. Initially, you’ll get a 404 (not found) but after while of that, it should switch to 403 (access denied)
for X in {1..100}; do curl -D - --silent https://www.some.org/$X | grep HTTP;done
HTTP/2 404
HTTP/2 404
...
...
HTTP/2 403
HTTP/2 403
Conclusion
Congrats! after much work you’ve traded 404s for 403s. Was it worth it? Probably. If an adversary’s probe had a chance to find something, it has less of a chance now.
Bonus Section
I mentioned earlier that the X-Forwarded-For
header could be spoofed. Let’s take a look at that. Here’s an example.
# Comment out 'client_ip_headers CF-Connecting-IP' from your Caddy config, and restart.
for X in {1..100}; do curl -D - --silent "X-Forwarded-For: 192.168.0.2" https://www.some.org/$X | grep HTTP;done
HTTP/2 404
HTTP/2 404
...
...
HTTP/2 404
HTTP/2 404
No remediation happens. Turns out Cloudflare appends by default, giving you:
sudo tail -f /var/log/caddy/www.some.org.log | jq
"client_ip": "192.168.0.2",
"X-Forwarded-For": [
"192.168.0.2,109.206.128.45"
],
Caddy takes the first value, which is rather trusting but canonically correct, puts it as the client_ip
and CrowdSec uses that.
Adjusting Cloudflare
You don’t need to, but you can configure Cloudflare to “Remove visitor IP headers”. This is counterintuitive, but the notes say “…Cloudflare will only keep the IP address of the last proxy”. In testing, it keeps the last value in the X-Forwarded-For
string, and that’s what we’re after. It works for normal and forged headers.
- Log in to the Cloudflare dashboard and select your website
- Go to Rules > Overview
- Select “Manage Request Header Transform Rules”
- Select “Managed Transforms”
- Enable Remove visitor IP headers
The Overview page may look different depending on your plan, so you may have to hunt around for this setting.
Now when you test, you’ll get access denied regardless of your header
for X in {1..100}; do curl -D - --silent "X-Forwarded-For: 192.168.0.2" https://www.some.org/$X | grep HTTP;done
HTTP/2 404
HTTP/2 404
...
...
HTTP/2 403
HTTP/2 403
Bonus Ending
You’ve added an extra layer of protection - but it’s not clear if it’s worth it. It may add to the proxy time, so use at your own discretion.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.