
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.36Edg/112.0.1722.58" "(null)" GET HTTPS miss 1295620230504110000 42.48.17.248 chat.0.bnu120.space /_astro/Generator.b93fa34a.js?0.0052218168987021585 373043 1466 26 200NULL 1995 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0Safari/537.36 Edg/112.0.1722.58" "(null)" GET HTTPS miss 1295620230504110230 171.105.47.120 chat.0.bnu120.space /_astro/Generator.aeb3c74f.js?0.8498590254257025 372943 173 2 200 NULL957 "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0" "(null)" GET HTTPS miss 5101920230504110248 61.162.51.53 chat.0.bnu120.space /?0.812562285017211 13208 122 26 200 NULL 294 "Mozilla/5.0 (Windows NT10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 6199320230504110333 123.138.86.242 chat.0.bnu120.space /_astro/Generator.aeb3c74f.js?0.6079898051319659 373055 152 26 200NULL 1347 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0Safari/537.36" "(null)" GET HTTPS miss 411620230504110331 123.138.86.242 chat.0.bnu120.space /?0.20331429440686866 13208 152 26 200 NULL 2401 "Mozilla/5.0 (WindowsNT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 411620230504110405 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 6462320230504110441 218.3.69.219 chat.0.bnu120.space /?0.023925127380459932 13207 120 2 200 NULL 251 "Mozilla/5.0 (Windows NT10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 5590020230504110521 124.78.41.146 chat.0.bnu120.space /?0.5546383597848501 13206 1050 2 200 NULL 56 "Mozilla/5.0 (Windows NT10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss 160020230504110600 171.221.240.229 chat.0.bnu120.space /?0.23147727705984766 13096 1068 2 200 NULL 109 "Mozilla/5.0 (WindowsNT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" "(null)" GET HTTPS miss14413
可以看到因为它每次访问都带了一个 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 的生态(真的贫)
xxxxxxxxxxfrom sanic import __version__ as sanic_version, Sanicfrom sanic_gzip import Compressfrom sanic.response import redirect, textfrom loguru import loggerimport 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 应用的输出