2023年5月4日,有人 DOS 攻击我
跑了我不到 40 块钱的流量
辛亏有上次被 Clash 的 Allow LAN 利用的经验,没充太多钱(那次损失了近 200 元)
后来发现是 free-chat 的 node 进程在跑流量,一结束就好了
但还是没法分出来恶意流量,直到我导出了腾讯云 CDN 的日志(下面截取头 10 行):
20230504110000 42.48.17.248 chat.0.bnu120.space /_astro/Generator.aeb3c74f.js?0.35109778096110755 0 1466 26 0 NULL 609
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
Edg/112.0.1722.58" "(null)" GET HTTPS miss 12956
20230504110000 42.48.17.248 chat.0.bnu120.space /_astro/Generator.b93fa34a.js?0.0052218168987021585 373043 1466 26 200
NULL 1995 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0
Safari/537.36 Edg/112.0.1722.58" "(null)" GET HTTPS miss 12956
20230504110230 171.105.47.120 chat.0.bnu120.space /_astro/Generator.aeb3c74f.js?0.8498590254257025 372943 173 2 200 NULL
957 "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0" "(null)" GET HTTPS miss 51019
20230504110248 61.162.51.53 chat.0.bnu120.space /?0.812562285017211 13208 122 26 200 NULL 294 "Mozilla/5.0 (Windows NT
10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 61993
20230504110333 123.138.86.242 chat.0.bnu120.space /_astro/Generator.aeb3c74f.js?0.6079898051319659 373055 152 26 200
NULL 1347 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0
Safari/537.36" "(null)" GET HTTPS miss 4116
20230504110331 123.138.86.242 chat.0.bnu120.space /?0.20331429440686866 13208 152 26 200 NULL 2401 "Mozilla/5.0 (Windows
NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 4116
20230504110405 2001:250:1001:a004::14d chat.0.bnu120.space /?0.30710401088826433 13097 152 38 200 NULL 137 "Mozilla/5.0
(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.58"
"(null)" GET HTTPS miss 64623
20230504110441 218.3.69.219 chat.0.bnu120.space /?0.023925127380459932 13207 120 2 200 NULL 251 "Mozilla/5.0 (Windows NT
10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 55900
20230504110521 124.78.41.146 chat.0.bnu120.space /?0.5546383597848501 13206 1050 2 200 NULL 56 "Mozilla/5.0 (Windows NT
10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 1600
20230504110600 171.221.240.229 chat.0.bnu120.space /?0.23147727705984766 13096 1068 2 200 NULL 109 "Mozilla/5.0 (Windows
NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss
14413
可以看到因为它每次访问都带了一个 queryString 所以缓存都 miss 了,这就是缓存击穿,所以流量全回源了
后来我紧急把 CDN 里的 queryString 调成了忽略全部(毕竟我这个应用确实没有用到 query String 的 url)
这样就能命中缓存了,但是还不够完美,因为:
CDN 还是被跑了很多流量给攻击者,虽然 CDN 的流量更便宜,但钱还是钱
想骂一下攻击者
我想起了之前不是有个作为热备源站,实则在停机更新时显示公告信息的网站的服务嘛,就给它改了改:
x...
app = Starlette()
...
route("/{path:path}") .
async def catch_all(request):
if "?0." in str(request.url):
return Response("FUCK YOU STOPPING HACKING ME!", status_code=403)
else:
return Response(None, status_code=404)
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=9042, reload=True)
但这个应用主要还是用来做静态文件服务的,前面也有很多 caching 的代码,和反攻击的代码放在一起就不太好。于是我又改写了之前用来重定向 index.chat.bnu120.space
到 ic.free-chat.asia
的 Sanic 服务,让它拥有了一样的功能,顺便了解了一下 Sanic 的生态(真的贫)
xxxxxxxxxx
from sanic import __version__ as sanic_version, Sanic
from sanic_gzip import Compress
from sanic.response import redirect, text
from loguru import logger
import aiohttp
app = Sanic(__name__)
compress = Compress()
SERVER_NAME = f'Sanic {sanic_version}'
route('/<path:path>') .
compress() .
async def handle(request, path):
if 'index.chat.bnu120.space' in request.host:
logger.info(f'{request.ip} - {request.method} {request.url}')
return redirect('https://ic.free-chat.asia/', status=301)
elif 'chat.0.bnu120.space' in request.host:
if '?' in request.url:
logger.warning(f'{request.ip} - {request.method} {request.url}')
return text('FUCK YOU! STOP HACKING ME!', status=403)
async with aiohttp.ClientSession() as session:
async with session.get(f'http://localhost:9041/{path}', headers=request.headers) as resp:
headers = resp.headers.copy()
headers.pop('Transfer-Encoding', None)
headers.pop('Content-Encoding', None)
headers["server"] = SERVER_NAME
logger.debug(f'{request.ip} - {request.method} {request.url}')
return text(await resp.text(), status=resp.status, headers=headers)
else:
logger.error(f'{request.ip} - {request.method} {request.url}')
return text('Invalid host', status=404)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, auto_reload=True)
还给它加了层压缩(可惜好像 Sanic 生态中没有好用的 brotli 压缩的库),实测能节省一半的带宽🤩
请求数没怎么减少(没截图)但流量和带宽大幅下降,算是搞定啦 ~
本文开头的图就是这个 Sanic 应用的输出