用cookies应对DDOS的Sanic中间件
最近网站遭到了DDOS,流量飙得老高,100GB的流量包半周就能耗完。发现攻击集中在 376 KB 的最大的bundle文件上。由于攻击来自多个IP,且(由于我比较菜)没有什么特征可以分辨,逐url逐IP的令牌桶没效果,所以一直都没有实质性的办法。
说起来,这人一开始还通过尾随 queryString 的方式击穿我CDN的缓存,跑了我源站几十GB的流量直接给我干欠费了 哈哈哈
今天突然想到了一种办法通过cookies的方式,让浏览器访问的友好用户可以正常访问,而恶意攻击不会占用我太大流量:
我先是创建了一个自动刷新的HTML:
<html>
<head>
<meta http-equiv="refresh" content="0">
</head>
</html>
还可以更短:
<meta http-equiv="refresh" content="0">
我的思路是,让所有请求都解析出这个HTML,但响应投中包含一个set-cookies,刷新后的请求发现cookies对上了就返回原始数据。这个方案的优势在于:
- 可以完全作为中间件存在,无需改动服务源码
- 用户无感,因为这个刷新实测非常快
事实上,这其实很容易被破解,攻击者只需要模仿浏览器执行set-cookies的行为即可。如果他确实这么做了,那我觉得只能把set-cookies放在
<script>
中实现,然后结合混淆的方式来改进了。毕竟要是要执行js的话,基本上就得用Puppeteer、Selinium之类的麻烦玩意儿了
欢迎大家开无痕打开Devtools去尝试尝试我部署了这个中间件的网页
附录
源码如下:
import aiohttp
from sanic import Request, Sanic
from sanic import __version__ as sanic_version
from sanic.response import empty, file, raw
app = Sanic(__name__)
@app.route("/<path:path>")
async def index(request, path):
async with aiohttp.ClientSession() as session:
async with session.get(
f"<protocol>://<host>:<port>/{path}", headers=request.headers
) as resp:
headers = resp.headers.copy()
headers.pop("Transfer-Encoding", None)
headers.pop("Content-Encoding", None)
return raw(await resp.read(), status=resp.status, headers=headers)
CHECK_KEY = "human"
CHECK_VAL = "Hi!"
@app.on_request
async def check_not_first(request: Request):
if CHECK_KEY in request.cookies:
if request.cookies.get(CHECK_KEY) == CHECK_VAL:
return
if "text/html" in request.headers.get("accept", ""):
response = await file("./redirect.html")
response.add_cookie(CHECK_KEY, CHECK_VAL)
return response
else:
return empty()