“CloudFlare+Nginx+iptables” – 中小型网站防御DDoS和CC攻击最有效且性价比最高的方法

如果是没有做过攻击防御的小网站遇到DDoS或CC攻击,会导致服务器无法被正常访问,或访问体验慢,甚至直接瘫痪宕机。不过,如果你没有得罪大佬的话,一般攻击时间不会很久,有可能被误伤了,但是如果你网站跟大佬有利益冲突,那么你只能上高防IP或关机保平安了。众所周知,DDoS攻击防御是一个世界性的难题,目前做得最好的当属Cloudflare。幸运的是,Cloudflare提供免费的无限级CC和DDoS攻击防御,所以一灯不是和尚建议各位中小站长提前做好攻击防御工作,以避免被攻击时损失更大。如果我们买不起高防服务器,或者觉得买高防性价比太低,那么我们可以选择Cloudflare的免费CDN,并配合Nginx和iptables配置规则进行低成本防御,但是要损失一些用户体验。虽然,Cloudflare的免费CDN在国外加速效果非常牛逼,但是在天朝境内的话,其CDN加速在部分地区被誉为云减速。

1、接入Cloudflare免费无限级防御

如果你的网站服务器被持续攻击,并丝毫没有缓解的征兆的话,我建议尽快接入Cloudflare解析,并开启CDN加速的攻击保护“Under Attack”模式,此时著名的5秒盾回自动开启。如下图所示:

这时候,任何访问你网站的用户都会看到一个5秒验证提示,此种方法对DDoS和CC攻击非常有效。

2、限制攻击频繁地域的IP访问

通常,很多攻击者使用僵尸网络来自于海外,以美国IP居多。如果你的网站主要面对中文用户的话,那么你可以直接限制国外IP对网站的访问;如果你的访客主要面对海外用户的话,那么你直接一直接入Cloudflare的CDN网络就可以了,毕竟Cloudflare的攻击防御能力是首屈一指的,还没有被打死过,而且非中国大陆地区的Cloudflare的免费版CDN加速效果也非常好;如果你的网站同时向全球用户提供访问,那么你只能先屏蔽较多攻击者所在地域的IP,等缓解之后在逐步调试。

另外,CloudFlare的防火墙还提供了Cookie、IP地址、主机名等定制化规则,你可以进行更加详细的攻击防御定制。一般情况下,我们将CloudFlare设置为质询就可以了,当然你也可以选择直接阻止。此时,我们在CloudFlare的防火墙日志中能看到已经阻止的攻击者IP了。如下图所示:

3、仅允许 Cloudflare IP 访问

现在,我们已经阻挡了大部分的攻击者了,但我们还需要对服务器做进一步的设置:仅允许CloudFlare IP访问。有些服务器在开启CDN后获取到的IP会是CDN的IP,并不是真实用户的IP地址,此时我们需要对Nginx或者Apache做进一步的调整。

(1)Nginx仅允许CloudFlare IP

有的时候我们需要借助Cloudflare 的安全防护功能来防止CC或者DDoS攻击,即仅允许Cloudflare CDN的IP访问我们的访问。

Nginx直接拒绝和允许IP访问代码示例如下:

location / {
deny 192.168.1.1;
allow 192.168.1.0/24;
allow 10.1.1.0/16;
allow 2001:0db8::/32;
#Railgun IP
deny all;
}

如果我们仅允许Cloudflare CDN的IP访问网站,我们可以直接在nginx配置中将Cloudflare CDN的IP添加到允许的范围内。

#直接加入
# 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:8100::/32;
allow 2405:b500::/32;
allow 2606:4700::/32;
allow 2803:f800::/32;
allow 2c0f:f248::/32;
allow 2a06:98c0::/29;

自动更新Cloudflare CDN的IP。手动添加Cloudflare CDN的IP到Nginx配置当中简单方便,但是一旦Cloudflare CDN的IP有变化时还得自己手动处理,我们可以创建一个脚本,定时去更新Cloudflare CDN的IP,自动添加到Nginx配置中,代码如下:

touch /usr/local/nginx/conf/allow_ip.conf
#修改网站nginx配置,加入以下代码:
include /usr/local/nginx/conf/allow_ip.conf;
vim /data/script/allow_cf_ip.sh
#!/bin/bash
echo "#Cloudflare" > /usr/local/nginx/conf/allow_ip.conf;
for i in `curl https://www.cloudflare.com/ips-v4`; do
echo "allow $i;" >> /usr/local/nginx/conf/allow_ip.conf;
done
for i in `curl https://www.cloudflare.com/ips-v6`; do
echo "allow $i;" >> /usr/local/nginx/conf/allow_ip.conf;
done
#添加定时任务
0 5 * * 1 /bin/bash /data/script/allow_cf_ip.sh

(2)iptables防火墙仅允许CloudFlare IP访问

添加cloudflare ips-v4到 iptables 白名单的命令

for i in `curl https://www.cloudflare.com/ips-v4`; do iptables -I INPUT -p tcp -m multiport --dports http,https -s $i -j ACCEPT; done

添加cloudflare ips-v6 iptables 白名单的命令

for i in `curl https://www.cloudflare.com/ips-v6`; do ip6tables -I INPUT -p tcp -m multiport --dports http,https -s $i -j ACCEPT; done

丢弃白名单以外的 ipv4 80,443 tcp 包

iptables -A INPUT -p tcp -m multiport --dports http,https -j DROP

丢弃白名单以外的 ipv6 80,443 tcp 包

ip6tables -A INPUT -p tcp -m multiport --dports http,https -j DROP

(3)Cloudflare获取访客用户的真实IP

有些服务器在开启CDN后获取到的IP会是CDN的IP,并不是真实用户的IP地址,此时我们需要对Nginx或者Apache做进一步的调整。

4、分析日志自动启用防火墙

(1)一键屏蔽指定地域IP访问

一键屏蔽指定国家所有IP访问的项目官网:https://github.com/iiiiiii1/Block-IPs-from-countries

原理是下载指定国家的IP段,然后将IP段添加到iptables规则当中,直接执行以下命令:

wget https://raw.githubusercontent.com/iiiiiii1/Block-IPs-from-countries/master/block-ips.sh
chmod +x block-ips.sh
./block-ips.sh

然后会提醒你,选择封禁IP,还是解封IP。如下图所示:

选择封禁IP后,会让你输入国家代码,请到这里查看:http://www.ipdeny.com/ipblocks,例如美国就是输入us,回车后完成对整个美国IP封禁。如下图所示:

(2)使用脚本定时检测日志

我们能否通过分析日志来确认恶意的访问请求呢?我们可以监控服务器日志,将请求出错的访问者 IP 放入一个 List 特别观察,在一段时间内如果没有太多的出错,我们就将其从列表中移除,否则,错误太多达到警戒值就调用 iptable 将其禁封。脚本代码如下:

#!/usr/bin/perl use strict;
use warnings; ## 本脚本将会监控 Web 服务器的 log 记录,(例如 Apache 或者 Nginx)
## 并统计同一个 IP 所引发的 HTTP 错误数目。该数值达到用户配置的数量,
## 则使用防火墙对该 IP 进行屏蔽,拒绝其访问。 ## log 文件路径
my $log = "/var/log/nginx/access.log"; ## 一个 IP 触发了多少次错误,我们就将其屏蔽?
my $errors_block = 10; ## 过期时间,超过多少秒没有再见到该 IP 则将其从观察列表中移除?
my $expire_time = 7200; ## 将 IP 从观察列表中移除时,清理多少个错误日志行数?
my $cleanup_time = 10; ## 调试模式 on=1 off=0
my $debug_mode = 1; ## 声明一些内部变量
my ( $ip, $errors, $time, $newtime, $newerrors );
my $trigger_count=1;
my %abusive_ips = (); ## 打开日志文件。使用系统的 tail 命令,有效轮询
open(LOG,"tail --follow=$log |") || die "Failed!\n"; ## For Linux (Ubuntu) systems
# open(LOG,"tail -f $log |") || die "Failed!\n"; ## For OpenBSD, FreeBSD or Linux systems while(<LOG>) {
## 定义错误代码。这里使用了正则表达式匹配,你可以自行添加一些,
## 例如无端访问 .vbs 后缀文件请求,列入屏蔽条件 
if ($_ =~ m/( 401 | 402 | 403 | 404 | 405 | 406 | 407 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 444 | 500 | 501 | 502 | 503 | 504 | 505 )/)
{ ## 自定义: 白名单 IP。 不论这些 IP 做了什么,均不屏蔽。
## Google 百度等爬虫 IP 段 加入到白名单中。
## 为了方便程序员开发测试,内部子网 192.168/16 也不屏蔽。
if ($_ !~ m/(^66\.249\.\d{1,3}\.\d{1,3}|^216\.239\.\d{1,3}\.\d{1,3}|^192\.168\.\d{1,3}\.\d{1,3}|^116\.255\.\d{1,3}\.\d{1,3}|^220\.181\.\d{1,3}\.\d{1,3}|^123\.125\.\d{1,3}\.\d{1,3}|^203\.208\.\d{1,3}\.\d{1,3}|^181\.108\.\d{1,3}\.\d{1,3}|^180\.76\.\d{1,3}\.\d{1,3}|^183\.60\.\d{1,3}\.\d{1,3}|^210\.72\.225\.\d{1,3}|^159\.226\.50\.\d{1,3}|^61\.135\.168\.\d{1,3})/)
{ ## 从日志行中解析出 IP
$time = time();
$ip = (split ' ')[0]; ## 若 IP 之前从未出现过,我们需要初始化,以避免出现警告消息
$abusive_ips{ $ip }{ 'errors' } = 0 if not defined $abusive_ips{ $ip }{ 'errors' }; ## 给这个 IP 增加出错计数,更新时间戳
$abusive_ips{ $ip }{ 'errors' } = $abusive_ips{ $ip }->{ 'errors' } + 1;
$abusive_ips{ $ip }{ 'time' } = $time; ## DEBUG: 输出详细信息
if ( $debug_mode == 1 ) {
$newerrors = $abusive_ips{ $ip }->{ 'errors' };
$newtime = $abusive_ips{ $ip }->{ 'time' };
print "unix_time: $newtime, errors: $newerrors, ip: $ip, cleanup_time: $trigger_count\n";
} ## 如果该 IP 已经触发 $errors_block 出错数量,调用 system() 函数屏蔽之
if ($abusive_ips{ $ip }->{ 'errors' } >= $errors_block ) { ## DEBUG: 输出详细信息
if ( $debug_mode == 1 ) {
print "ABUSIVE IP! unix_time: $newtime, errors: $newerrors, ip: $ip, cleanup_time: $trigger_count\n";
} ## 自定义: 这里是屏蔽 IP 的 system() 系统调用
## 你可以对这个 IP 添加更多的执行操作。例如,我们使用 logger 打印记录到 /var/log/messages 
## 注释掉的是 OpenBSD 系统 Pf 防火墙
system("logger '$ip blocked by calomel abuse detection'; iptables -I INPUT -s $ip -j DROP");
# system("logger '$ip blocked by calomel abuse detection'; pfctl -t BLOCKTEMP -T add $ip"); ## 当 IP 已经被屏蔽,它就没必要继续留在观察列表中了
delete($abusive_ips{ $ip });
} ## 为后面的清理函数增加触发计数
$trigger_count++; ## 清理函数:当触发计数达到 $cleanup_time 我们将所有已经过期的条目从 $abusive_ips 列表中删除
if ($trigger_count >= $cleanup_time) {
my $time_current = time(); ## DEBUG: 输出详细信息
if ( $debug_mode == 1 ) {
print " Clean up... pre-size of hash: " . keys( %abusive_ips ) . ".\n";
} ## 清理我们已经很久没再见到的 IP
while (($ip, $time) = each(%abusive_ips)){ ## DEBUG: 输出详细信息
if ( $debug_mode == 1 ) {
my $total_time = $time_current - $abusive_ips{ $ip }->{ 'time' };
print " ip: $ip, seconds_last_seen: $total_time, errors: $newerrors\n";
} ## 如果 IP 未出现的时间已经超过我们设定的过期时间,则将其从列表中移除
if ( ($time_current - $abusive_ips{ $ip }->{ 'time' } ) >= $expire_time) {
delete($abusive_ips{ $ip });
}
} ## DEBUG: 输出详细信息
if ( $debug_mode == 1 ) {
print " Clean up.... post-size of hash: " . keys( %abusive_ips ) . ".\n";
} ## 重置清理触发计数
$trigger_count = 1;
}
}
}
}
#### EOF ####

保存以上内容到 web_server_abuse_detection.pl,增加可执行权限。命令如下:

chmod +x web_server_abuse_detection.pl

设置 my $debug_mode = 0; 脚本即会静默运行。要让它在后台运行,不占用终端,则在命令后加一个 & 符号

./web_server_abuse_detection.pl &

5、使用Cloudflare对DDoS和CC攻击防御总结

从使用体验来看,开启Cloudflare的5秒盾是解决中小型网站防御DDoS和CC攻击最有效且性价比最高的方法,而且可以利用Cloudflare自带的防火墙可以对攻击者的IP进行区别对待,从而最大限度地提高防御效果。另外,如果你的网站架设在国内运营商的服务器上,那么很遗憾,当你开启Cloudflare的时候可能已经被打进黑洞了。尤其,像阿里云、腾讯云这些大厂的云主机,一旦遭遇到攻击且超过5G防御峰值直接进入空路由的黑洞状态,10小时以后再把你放出来。遇到这种情况,我们应该怎么办?一灯不是和尚建议你使用国内大厂的虚拟主机服务,共享资源的那种,防御峰值较高,而且一般不会直接给你黑洞,即使宕机还可以重启。当然,我还是推荐你使用国外的VPS或独立服务器提供商,比如美国的搬瓦工(BandwagonHOST)DMIT或 iON Cloud 等,被打进黑洞的状态较短(一般不超过1小时),且提供高防服务器。

本文由一灯不是和尚于2021年9月16日更新;如果您有什么意见或建议,请在文章下面评论区留言反馈。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注