VPS主机防攻击应对CC和DDOS的基本思路-防扫描防火墙阻止策略

越来越多的人使用VPS主机建站,但是与虚拟主机或者管理型的服务器相比,个人VPS主机基本上是无管理型的,即主机商只负责VPS主机的网络畅通,至于技术上的问题都得靠自己来解决。之前用阿里云的VPS主机经常受到CC和DDOS攻击,基本上每次一被攻击就要进入阿里云的“黑洞”。

阿里云对待CC和DDOS的VPS主机一般不能防御太多流量,一旦攻击流量起来了基本上会清空路由,如果被攻击的次数多了话甚至会长达一天或几天的“黑洞”。现在挖站否搬家到了经典CN2 VPS搬工上,之前有收到过热心朋友的提醒,搬工对待CC和DDOS的次数只有3次,超过3次直接封掉VPS。

这次的攻击事件也让我知道了将网站放在经典VPS主机上依然要保持一种小心谨慎的态度。在最开始挖站否无法打开,接着受到不少朋友发来的信息提醒,我马上登录到主机商后台查看,从监控图表中可以看出VPS的CPU占用超过100%,带宽速度已经超过上百MB,显然VPS是被主机商给断网了。

本篇文章就来分享一下使用VPS主机建站的朋友,应对小型的CC和DDOS攻击的基本步骤,基本是围绕防火墙阻止恶意IP来展开的。

更多的关于网站建设的网站安全与经验,还有:

  1. 五条关于使用免费VPS控制面板的安全建议-不让黑客有可趁之机

  2. Linux的php-fpm优化心得-php-fpm进程占用内存大和不释放内存问题

  3. DNS域名解析启用DNSSEC防止DNS劫持-Google Cloud DNS设置DNSSEC


一、应对CC和DDOS要提前做好功课

以下两个命令大家可以快速对当前攻击做出判断:输入下面命令后查看单个IP的连接数。

netstat -nat|grep -i '80'|wc -l


对连接的IP按连接数量进行排序,查看TCP连接状态。

netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n


1.1 勤备份-本地与异地备份相结合

网站备份是应对CC和DDOS攻击的万全之策,无论如何攻击,只要数据没有被破坏,我们就可以将损失降低到最少。对于数据更新频繁的建议一天备份一次,有些主机商还会提供快照备份功能,例如阿里云、腾讯云以及之前介绍的Linode快照等。

除了本地备份,自然还是做好异地备份。异地备份可以结合云存储或者云盘来保存数据,之前分享的Linux VPS挂载Google Drive和Dropbox就可以自动将本VPS上的数据同步上传到网盘中,或者使用DigitalOcean Spaces云存储、又拍云、七牛云存储、百度BOS等专业云存储。

1.2 学会查看Linux VPS系统负载

一旦遭受到了CC和DDOS攻击,一般我们都可以通过VPS主机商的控制面板来查看,我们可以看到自己的监控数据有明显的“异常”。下图是我遭受攻击时VPS主机监控显示的IO、CPU、内存等呈现100%占用的情况。(点击放大)

另外,对于Linux初学者还需要掌握基本的查看CPU、内存、IO等系统指标的命令,VPS主机商的面板监控数据有延迟,而直接使用系统自带的监控命令可以实时查看数据更新,这里有一个专题:Linux系统监控命令整理汇总-掌握CPU,内存,磁盘IO等找出性能瓶颈。

1.3 掌握网站日志分析方法及工具用法

任何CC和DDOS攻击都会在网站日志中留下“蛛丝马迹”,常见的就是单个IP在短时间内出现大量的请求,或者是恶意IP扫描后台。对于大量日志文件,可以使用日志分析工具:服务器日志分析利器:ngxtop和GoAccess-实时监控可视化管理快速找出异常来源。

对于一些IP基本上放入IP库中就可以查出是有问题的IP。

二、应对小型CC和DDOS攻击方法

2.1 启用Cloudflare防攻击模式

有人会产生疑问:为什么不说说其它的CDN防攻击?因为根据我个人的测试,例如360网站卫士等免费防护功能,在网站遇到强烈的攻击时基本上是回源,效果几乎没有。而Cloudflare这个功能的主要作用是访问任何页面的时候强制在 CF 的认证页面停留 5 秒,效果非常好用。

2.2 巧用Fail2ban阻止攻击IP

fail2ban是由Python语言开发监控软件,通过监控系统日志的登录信息来调用iptables屏蔽相应登录IP,以阻止某个IP恶意访问。fail2ban读对应日志文件,Debian/Ubuntu:/var/log/auth.log、CentOS/Redhat:/var/log/secure。安装fail2ban:

apt-get install fail2ban
#CentOS内置源并未包含fail2ban,需要先安装epel源
yum -y install epel-release
#安装fial2ban
yum -y install fail2ban


安装完成后主要的配置文件在/etc/fail2ban目录下,里面有两个文件和两个目录:
fail2ban.conf :配置文件里面定义了fail2ban记录的日志等级、日志文件的位置以及socket。

jail.conf 里面定义了对那些服务进行监控,以及使用的一些策略。

jail.conf 里面开头是默认全局配置块[DEFAULT],默认配置说明如下:

[DEFAULT]
#忽略哪些IP,可以是具体IP、CIDR类型的地址,多个IP用空格分开
ignoreip = 127.0.0.1
#设置IP被锁住的时间,单位为秒
bantime = 600
#检测时间,在此时间内超过规定的次数会激活fail2ban
findtime = 600
#尝试的次数
maxretry = 3
#日志检测机器,有"gamin", "polling" and "auto"三种模式。
backend = polling
#发送报警邮件的地址
destemail = root@localhost #默认的动作执行行为,在action.d目录下有各种行为策略,默认是iptables-#multiport
banaction = iptables-multiport
#0.8.1版本后fail2ban默认用sendmail MTA
mta = sendmail
#默认使用tcp协议
protocol = tcp
#定义了各种行动的参数
#banaction参数在action.d目录下具体定义,name port protocol 也可以自己定义
#只禁止IP
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s]
#即禁止IP又发送email
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s]
#禁止IP、发送email、报告有关日志
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s]
%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s]
#如果没有定义行为,则默认的行为为action,可选择action_,action_mw, action_mwl 等
action = %(action_)s
默认配置文件含有此模块
#定义子模块名
[ssh]
#是否激活
enabled = true
#定义port,可以是数字端口号表示,也可以是字符串表示
port= ssh
#过滤规则,在filter.d目录下定义
filter = sshd
#检测日志的路径
logpath = /var/log/auth.log
#尝试的次数,覆盖了全局配置的
maxretry = 6
#banaction 在action.d目录下定义,此参数值会替换action中选用的默认行为中定义的banaction参数
banaction = iptables-allports
#注意 port protocol banaction 可以不用分开定义,直接使用action定义也可以,例如:
#action = iptables[name=SSH, port=ssh, protocol=tcp]
#在子模块中定义的port protocol banaction 都会在action_ action_mw, action_mwl中替换成具体的设置值。


Fail2ban其它的目录功能说明如下:
filter.d 目录里面定义的是根据日志文件进行过滤的规则,主要是利用正则匹配出现错误的关键字。

action.d目录里面是根据过滤的规则对相应的IP采取什么样的行为 。

Fail2ban防CC攻击示例:

#需要先新建一个nginx日志匹配规则
vi /etc/fail2ban/filter.d/nginx-cc.conf
#填写如下内容
[Definition]
failregex = <HOST> -.*- .*HTTP/1.* .* .*$
ignoreregex =


继续修改jail.local追加如下内容:

[nginx-cc]
enabled = true
port = http,https
filter = nginx-cc
action = %(action_mwl)s
maxretry = 60
findtime = 60
bantime = 3600
logpath = /data/wwwlogs/wzfou.com_nginx.log


上面的配置意思是如果在60s内,同一IP达到60次请求,则将其IP ban 1小时,记得修改你的网站日志路径。使用以下命令查看fail2ban状态,显示出被禁止IP地址列表。

/usr/local/python/bin/fail2ban-client status ssh-iptables



经过挖站否wrfou.com的测试,使用Fail2ban应用一般的CC攻击基本上没有什么问题。

2.3 使用脚本定时检测日志发现异常

所有对服务器的访问请求都会在 Nginx 日志中记录,这其中也包括那些造成出错的请求。我们能否通过分析日志来确认恶意的访问请求呢?我们可以监控服务器日志,将请求出错的访问者 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_countn";
} ## 如果该 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_countn";
} ## 自定义: 这里是屏蔽 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: $newerrorsn";
} ## 如果 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 $log 是要监控的日志路径。日志文件格式是标准的 Apache “common” 或 “combined”。这个脚本可以处理 Apache, Nginx, Lighttpd 甚至 thttpd 的日志,它将会在日志中寻找第一个字符串,即远程连接过来的 IP 地址。

my $errors_block 是一个客户端所能触发的最大错误数量,超过此值 IP 将被屏蔽。 Web 服务器的错误代码 400-417(如果你用 Nginx 则还包括 444),以及 500-505 都是触发条件。我们默认设置 errors_block 为 10,即如果一个 IP 在 7200 秒 ($expire_time) 以内触发了 10 个错误 ($errors_block),它将被屏蔽。

my $expire_time 是一个 IP 从观察列表中移除的过期时间。默认我们设 7200 秒(2 小时)。请注意,一个 IP 必须在 7200 秒以内没有触发任何错误,我们才会将它从列表中移除。这意味着一个恶意用户用很慢的速率扫描,我们仍可能会将其屏蔽。例如,一个 IP 每一小时访问一次来检测漏洞,第一个小时它就被列入观察列表,在第二个小时出错计数被增加,同时“最后一次见到这个IP” 的时间戳也被更新。普通的入侵检测系统 (IDS) 可能会漏报这样的行为,但这个脚本会在一个较长的时间(2小时)持续跟踪监测一个 IP。在这个 IP 达到触发 10 次错误时,也就是10小时后,我们仍会将其屏蔽。

my $cleanup_time 是触发清理观察列表的出错行数量。清理工作是一个循环,很耗费 CPU 所以没必要每一个错误日志行都去执行。请保证你的清理计数值足够低,从而让旧 IP 能以合适的速率从列表中移除。但是太低又会耗费 CPU,一个恰当的值应该是你的服务器5分钟内所产生的错误日志行数。

my $debug_mode 调试模式,会打印出一些有用的信息。

自定义白名单:注释中已经说过,Google 机器人 IP段 66.249/16 就应该放到白名单里,因为任何别人的网站链接到我们的一个错误的 URL,都会导致搜索引擎抓取失败。开发人员的 IP 也应该放入白名单,因为程序测试也会经常产生失败错误。

自定义系统调用:检测到恶意 IP 后,我们通过系统调用屏蔽之。默认的调用包括 logger 打印消息到 /var/log/messages,并执行 iptables 屏蔽命令。我们也可以添加更多操作,例如触发一个 Nagios 监控警告,给运维人员发 Email,等等。

运行:

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

./web_server_abuse_detection.pl &


2.4 巧用Nginx的日志功能配合防火墙

首先给Nginx添加一个只有访客IP的日志格式,在nginx.conf的http层加入以下代码:

log_format iponly '$remote_addr';


重启nginx,就会多一个名为iponly的日志格式。顺便提一下:$remote_addr是HTTP请求头,此变量包含访客真实IP地址。然后修改需要开启防CC的网站配置文件,在server层加入以下代码:

 access_log /var/log/nginx/iponly.log iponly;


除非nginx对日志目录有写入权限,否则要把这一步也执行了:

touch /var/log/nginx/iponly.log


然后重启nginx,这样该站点就会多一个只记录访客IP的日志。其中的/var/log/nginx/iponly.log是日志的路径,可以自行更改。 刷新一下网站,看看日志文件里面有没有出现真实IP地址。有的话就代表一切正常。

接下来是最关键的一步,写监控该日志文件的Shell Script。

#!/bin/bash
#函数ban_now
ban_now() {
#输出IP的内容
echo $1
#执行iptables对该IP封禁
iptables -I INPUT -s $1 -p all -j DROP
#封禁后执行mail命令,给指定邮箱发一封邮件
echo -e "IP:$1 was banned at $(date).nniptables filter tables:nn$(iptables -L -n -t filter)" | mail -s "IP:$1 was banned at $(date)" your@email.com
}
#循环的开始
while [ "$loop" = "" ]
do
#清空日志文件
cat>/var/log/nginx/iponly.log</dev/null 2>&1
#合并,排序IP,输出获取请求数最大的IP及其请求数,请求数与IP之间使用英文逗号隔开,然后赋值给connections
connections=$(cat /var/log/nginx/iponly.log | sort -n | uniq -c | sort -nr | awk '{print $1 "," $2}')
#判断变量connections是否为空
if [ "$connections" != "" ];then
#输出变量connections的内容
echo $connections
#连接数的for循环开始
for ipconntctions in $connections
do
#截取连接数
connectnumber=$(echo $ipconntctions | cut -d "," -f 1)
#判断该IP连接数是否大于200
test $connectnumber -ge 200 && banit=1
#大于200,把IP赋值给变量fuckingip
if [ "$banit" = "1" ];then
fuckingip=$(echo $ipconntctions | cut -d "," -f 2)
ban_now $fuckingip
unset banit
else
#否则,结束for循环
break
fi
done
fi
done


因为要该Shell Script一直执行,因此使用while…do…done制作了一个“死循环”。在第六行写了一个函数ban_ip(),是使用iptables对IP进行封禁,并且完成后给我的邮箱发一封邮件。标题为:“IP和封禁时间”。内容为IP封禁时间和iptables的filter表。

保存以上代码后,赋予x(执行)权限。要实现该Shell Script在后台类似daemon,就需要使用nohup这个东西了:

nohup 该shell script的位置 >/dev/null 2>&1 &


>/dev/null 2>&1表示把执行该shell script所输出的信息(shell script中的几个echo,调试是时候用的。)重定向至/dev/null里面。 最后面的&代表后台执行。这样的话,即使注销了登录,该Shell Script也一样会在后台执行。

三、总结

以上两个实时检测网站日志并自动将异常IP加入到iptables中屏蔽的脚本我之前用过,效果很不错,足以应付小型的CC攻击。目前使用Fail2ban用于检测网站日志并屏蔽恶意IP。三个脚本各有各的优势,大家可以根据自己的需要来选择。

但是如果遇到较大的DDOS攻击还是得借用专门的防攻击服务,例如Cloudflare防攻击服务就非常地有效果,基本上搞VPS这一行的都用到它。如果你的网站一直被攻击得无法访问,可以升级Cloudflare到付费版,这样防护效果会更好。