11

I've recently gotten denial of service attacks from multiple proxy ips, so I installed cloudflare to prevent this. Then I started noticing that they're bypassing cloudflare by connecting directly to the server's ip address and forging the host header.

What is the most performant way to return 403 on connections that aren't from the 18 ip addresses used by cloudflare?
I tried denying all then explicitly allowing the cloudflare ips but this doesn't work since I've set it up so that CF-Connecting-IP sets the ip allow tests for.

I'm using nginx 1.6.0.

7 Answers7

9

The only solution I came up with that can be done with nginx by itself requires nginx version 1.9.7 or higher.

You can use the ngx_http_geo_module to identify and return a 403 response for any ip's that aren't cloudflare ip's.

Using this geo block.

geo $realip_remote_addr $cloudflare_ip {
    default          0;
    103.21.244.0/22  1;
    103.22.200.0/22  1;
    103.31.4.0/22    1;
    104.16.0.0/12    1;
    108.162.192.0/18 1;
    131.0.72.0/22    1;
    141.101.64.0/18  1;
    162.158.0.0/15   1;
    172.64.0.0/13    1;
    173.245.48.0/20  1;
    188.114.96.0/20  1;
    190.93.240.0/20  1;
    197.234.240.0/22 1;
    198.41.128.0/17  1;
    199.27.128.0/21  1;
    2400:cb00::/32   1;
    2405:8100::/32   1;
    2405:b500::/32   1;
    2606:4700::/32   1;
    2803:f800::/32   1;
    2c0f:f248::/32   1;
    2a06:98c0::/29   1;
}

You can then add this to your server block.

if ($cloudflare_ip != 1) {
    return 403;
}

Which will return a 403 for any connections not originating from a $cloudflare_ip.

This works because I'm using $realip_remote_addr in the geo block which keeps the original client address when using real_ip_header CF-Connecting-IP.

9

As described here, you can only allow ip addresses from cloudflare.

https://erichelgeson.github.io/blog/2014/01/18/whitelisting-cloudflare-in-nginx/

# https://www.cloudflare.com/ips
# IPv4
allow 103.21.244.0/22;
allow 103.22.200.0/22;
allow 103.31.4.0/22;
allow 104.16.0.0/12;
allow 108.162.192.0/18;
allow 131.0.72.0/22;
allow 141.101.64.0/18;
allow 162.158.0.0/15;
allow 172.64.0.0/13;
allow 173.245.48.0/20;
allow 188.114.96.0/20;
allow 190.93.240.0/20;
allow 197.234.240.0/22;
allow 198.41.128.0/17;

# IPv6
allow 2400:cb00::/32;
allow 2405:b500::/32;
allow 2606:4700::/32;
allow 2803:f800::/32;
allow 2c0f:f248::/32;
allow 2a06:98c0::/29;

deny all; # deny all remaining ips

Note that you will have to update this configuration every one in a while as Cloudflare's IP address ranges might change. To autogenerate this configuration you can use this script

mkg20001
  • 398
user6327
  • 191
3

The most performant way is a hardware firewall in front of the server. Or asking your datacenter/upstream provider for help mitigating attack.

Block things it in the webserver or iptables may help, but still uses bandwidth and system resources so DoS attacks are still possible. What you want is to block the traffic as far upstream as possible - so the traffic never reaches your server, and doesn't flood your link to the rest of the world. A hardware firewall can filter traffic much, much faster than your webserver, and uses no server resources. You will want them to allow traffic from cloudflare as well as your office or other servers for when you need to connect directly.

Changing the IP of the server may help as well - only cloudflare should need to know the new IP, don't publish it in public DNS records.

Grant
  • 18,125
  • 14
  • 75
  • 104
1

Here's a one-liner that generate the geo mapping from Cloudflare's IPs. Not necessarily the cutest one, perhaps a pro can correct me. I'm unable to fetch and parse v4 & v6 in one command because the files lack a carriage return at the end and the first v6 address is concatenated to the last v4.

(echo -e "geo \$realip_remote_addr \$cloudflare_ip {\n\tdefault\t0;" && curl -s https://www.cloudflare.com/ips-v4 | sed "s|^|\t|g" | sed "s|\$|\t1;|g" && echo -e  && curl -s https://www.cloudflare.com/ips-v6 | sed "s|^|\t|g" | sed "s|\$|\t1;|g" && echo -e "\n}")>cloudflare-only.conf

Inspired by a55 script above

1

I have merged the answers from jhilliar and from user6327 to get something OK to me. When using Cloudflare, you usually wish getting the origin client IP in your logs thanks to "set_real_ip_from".

But mixing set_real_ip_from and the solution given by user6327 does not work (as pointed by Sky). That's why Jhilliar solution with geo module should be used (and it seems to be more flexible by the way).

But it would be better to use a script to update cloudlfare IP list as user6327 has suggested. So here's the script.

wallass
  • 11
1

As continue of my old comment, while comments are not editable
i write a new bash script

  • checks received data before save
    to prevent breaks (deny all traffic)
  • i will update this script in future too


create a file /etc/nginx/cf_update.sh like:

#!/bin/bash

CONFIG_PATH="/etc/nginx/CF_Only.conf"

RULES=$(curl -ks "https://www.cloudflare.com/ips-v{4,6}" -w "\n" |sed "s/^/allow /g" |sed "s/$/;/g" && printf "\ndeny all;")

LINES=$( wc -l <<< $RULES )

if [[ $LINES -gt 8 ]]; then echo "$RULES" > $CONFIG_PATH else # send a warn to admin, but dont change the config echo "$(date)| Cant load cf ip list" >>/var/log/cf_update.log fi

Make it executable chmod +x /etc/nginx/cf_update.sh



Then run sudo crontab -e and add config:

1 2 * * * /etc/nginx/cf_update.sh

( to run only 1 time every day, at 02:01AM )
Save and exit



make your nginx config like:

server {
    listen              80;
    server_name         example.com;
    root                /home/www/site/example;
    include             CF_Only.conf;
    location ~ \.php$   {include php.conf;}
}

(default nginx path is /etc/nginx/)

a55
  • 161
0

Run this script to get all the IPs authorized by cloudflare

#!/usr/bin/bash

set -e

cf_ips() { echo "# https://www.cloudflare.com/ips"

for type in v4 v6; do echo "# IP$type" curl -sL "https://www.cloudflare.com/ips-$type/" | sed "s|^|allow |g" | sed "s|$|;|g" echo done

echo "# Generated at $(LC_ALL=C date)" }

(cf_ips && echo "deny all; # deny all remaining ips") > /etc/nginx/allow-cloudflare-only.conf

--

# reload Nginx
# sudo systemctl reload nginx

Note that Cloudflare might update its IP addresses at some point. You’d better run this script regularly or use the cron job to update the IPs.

For example, put this script under the weekly cron job directory as /etc/cron.weekly/update-cfips, give it execution permission with sudo chmod u+x /etc/cron.weekly/update-cfips. And comment out the sudo systemctl reload nginx line.