守卫你的 Arch Linux

前不久,在 Linode 上租的 VPS 满一年,当初初始化服务器的时候,选择的发行版是 Arch Linux,本着一颗骚包且追新的心。 中间遇到了不少坑,去年写过一篇文章记录

暴涨的流量

续费之后,跑在这台服务器上的主打网站突然流量暴涨,毫无理由的。 我的被害妄想症又冒出来,猜测是不是专门有人盯着这种续费了的 Linode 下手,耗光当月流量、拖垮服务器之类。 幸好醒悟得比较快,开始动手排查,并最终解决了问题,以下是过程。

首先看的是 /var/log/auth.log,看有否可疑的 SSH 访问,结果看到不少人在暴力破解 root 账号, 每秒请求一次的那种,温水煮青蛙的方式。幸好,没看到成功的。

然后看了看 /var/log/vsftpd.log,看看 FTP 这边厢怎么样,结果同样看到不少人在暴力破解账号, 猜测了几个用户名在不停地试,有一个用户名猜中了。再次庆幸,没看到成功的。

至此,流量暴涨的原因仍未找到,在暴涨的这几天里,流量高得诡异的阶段与以往网站访问高峰期基本符合, 所以,两个猜测:1)网站真的愈发知名了,越来越多的人再用;2)有人搞了一堆肉机在搞破坏,只要肉机一开机, 就不停地访问这个网站。

多猜无用,看看 /var/log/nginx/access.log 文件一探究竟。发现几条很诡异的:

222.214.74.145 - - [05/Mar/2013:11:11:19 +0000] "GET / HTTP/1.1" 200 75152 "-" ""
NSPlayer/12.00.7600.16597 WMFSDK/12.00.7600.16597"

这种访问,每秒都有好多条,日志被刷出好几G(这里和 cronie 没有跑起来,logrotate 没能表现一下也有关)。 总之打开日志当场吓尿,NSPlayer 究竟何方神圣?为何一秒内都要访问好几次?而且只请求 /, 首页相关的资源却全不放过?Google 一番,都说是 Windows Media Player,这货还是我用的第一个 PC 上的音乐播放器哇,怎么今时今日,会做这种令人不齿之事?

没时间一个个解惑,当务之急,是把这种请求过滤掉,不然,3月才过去5天,35% 的流量已经消耗掉了, 到时只能喝西北风去。

找准关键词,nginx ban by http agent,Google 一番即可找到类似示例:

location / {
    if ($http_user_agent ~* (NSPlayer)) {
        access_log off;
        return 200;
    }
}

把这一段配置,放到 /etc/nginx/nginx.conf 或者你切分好的配置文件里,server 章节里面, 然后 systemctl reload nginx 或者 rc.d reload nginx 即可。

这段代码的意思是,如果用户代理(HTTP User-Agent)是 NSPlayer,就直接返回一个空的 200, 并不记录在访问日志里。也可以把 200 换成 403 之类,更加严厉一些。

如何应对暴力破解 SSH

nginx 的访问日志终于清净了,可是 vsftpd 和 sshd 的,都还很恼人,破坏分子什么的最讨厌了。 对于 sshd,有个很简单直接的办法,就是在 /etc/ssh/sshd_config 里,把这两项改成 no:

PasswordAuthentication no
PermitEmptyPasswords no

并把自己电脑上 ssh 公钥丢到服务器上 ~/.ssh/authorized_keys 里。确定自己可以免密码登录后, 果断 systemctl reload sshd。如果你配对的私钥是有密码保护的,可以用 ssh-agent 减轻一些负担, 请看旧文 ssh-agent

然后,对付暴力破解 FTP 的,有两个办法,1)换端口,不用默认的 21,2)IP 黑名单或者白名单。前者, 暂时不考虑,我尝试的是后者,因为最早接触的,就是 /etc 下那两个文件 /etc/hosts.allow/etc/hosts.deny,可惜一直未曾一亲芳泽,没有真正接触过。

搜索相关资料一番,大惊失色,Arch Linux 上的 vsftpd 竟然不支持 /etc/hosts.deny, 因为后者依赖 tcp_wrappers 模块,而它因为年代久远已经被 Arch 的维护者们果断抛弃了, 在该文中, 维护着说,实在想不到有什么是 tcp_wrappers 能做,而 iptables 不能做的。

于是,找了一些 iptables 的资料,和封特定 ip 的简单用法,写了一段小脚本,从 hosts.deny 文件读取需要封禁的 IP,自动加到 iptables 里头去:

#!/bin/zsh
for ip in `cat /etc/hosts.deny | sed -e 's/^[a-z]*: //'`; do
  iptables -I INPUT -s $ip -j DROP
done

做法很简单,要封某个 IP,就在 iptables 里加一条记录 iptables -I INPUT -s bad.i.p.addr -j DROP, 所有提交(-I INPUT)到这台机器、来源是恶意 IP(-s bad.i.p.addr) 的,一律跳转到 DROP, 即丢弃这些请求包,包括(TCP 和 UDP)协议。更详细的做法,应该说明一下具体什么协议和端口,不然, 这条容易误杀,但先不管这么多了,iptables 还用不熟。

这是动态加的规则,重启 iptables 会没掉的,iptables-save > /etc/iptables/iptables.rules 保存一下。

以上,就是我做的救火工作,希望有空可以把这些补充完善,能够自定义一些合理的预警机制,单单依靠 Linode, 是不太够的。

2013-03-08 补充

感谢 @hugozhu 的分享,使用一段简单的 Bash 脚本,就可以从日志文件自动分析,无需手工维护 hosts.deny 文件:

#!/bin/bash
last_ip=""
tail -f /var/log/vsftpd.log | while read LINE; do
{
    if [[ "${LINE}" =~ "FAIL LOGIN" ]]; then
        ip="$(echo ${LINE} | awk '{ip=$NF;gsub(/\"/,"",ip);print ip}')"
        if [[ "$last_ip" == "$ip" ]]; then
             echo "block $ip"
             iptables -A INPUT -s "$ip" -j DROP
        fi
        last_ip=$ip
        echo $LINE
    fi
}
done

# nohup /root/bin/guard.sh > /var/log/guard.log 2>&1 &