登录页写着每分钟允许输错5次密码,超过就封。我试了9次,一次都没被拦。
去查应用里其他限流规则,同样形同虚设。修的过程中撞见第二个问题:生产环境里,所有用户在我的应用看来都是同一个IP。没人发现,因为限流器压根没在工作。要是只修好第一个bug,限流器会开始运转——用一个计数器,统计整个互联网。
![]()
限流器为什么什么都没做
在ASP.NET Core里,每个请求都要穿过一条中间件链,一步接一步,顺序决定结果。
其中一步是路由,它负责判断"这个请求是发给登录接口的"。限流器需要这个答案,因为每个接口有自己的限额。
我的应用把限流器放在了路由前面。它太早去问"这是哪个接口",没拿到答案,就放行了。没有报错,日志里也什么都没有。
修复只需要挪一行:
- app.UseRouting();
- app.UseRateLimiter(); // 必须放在路由之后
如果你从不自己调用 UseRouting(),ASP.NET 会在最前面替你加上,那你就碰不到这个bug。我是在链路靠后的位置自己调用的,限流器就排到了它上面。
改完之后限额开始生效,另外三个问题跟着冒了出来。它们一直都在,只是限流器不工作时伤不到任何人。
每个用户都是同一个IP
我的限额按IP地址计数。但应用躲在 Cloudflare 和 nginx 后面,从不直接和用户对话。用户的真实IP是通过 X-Forwarded-For 这个请求头传进来的。
nginx 这样构造这个头:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
$proxy_add_x_forwarded_for 会把收到的头拿过来,再加上连到 nginx 的那个地址。在我的部署里,那是跑在同一台机器上的 cloudflared。于是每个请求到达时都长这样(用户IP仅为示例):
- X-Forwarded-For: 203.0.113.42, 127.0.0.1
- 203.0.113.42 是真实用户,127.0.0.1 是 nginx 加上的 cloudflared
- 修复前,我的应用取的是:127.0.0.1
- 修复后,我的应用取的是:203.0.113.42
ASP.NET 的转发头中间件默认只读一项,也就是最后一项。而最后一项永远是 127.0.0.1。所以我那条"每个IP允许3次访客注册"的限额,实际会变成"所有人加起来3次"。
同一个地方还藏着第二个问题。我的配置信任任何人发来的这个头。生产环境里第一个问题把它盖住了,但如果没有那层额外的 nginx 跳转,客户端可以往头里写任意IP,换一个就拿到一个全新的计数器。我在测试环境里验证过,确实可行。
两个问题的修复大致如下:
- o.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
- o.ForwardLimit = null; // 读整个列表,而不是只读最后一项
- 只信任自己的跳转:回环地址和私有网段
- KnownIPNetworks 清空后,依次加入 127.0.0.0/8、::1/128、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16
现在中间件从列表末尾往前读,跳过它信任的地址(也就是我自己的代理),停在第一个不信任的地址上。那就是真实用户。如果客户端往头里塞了假IP,它会排在更靠左的位置,中间件根本读不到。
十次错误密码能锁死所有人
我的登录限额根本不是按IP算的。它是全站一个计数器,每分钟10次。
限流器不工作时,这无所谓。限流器一开,任何人发十次错误密码,就能把所有人的登录关掉,密码重置也一样,因为用的是同一个限额。我把它改成了每个IP一个计数器。攻击者仍然有10次机会,其他人照样能登录。
我的测试之所以全绿,正是因为那个bug。限额开始生效后,集成测试开始失败。它们会快速发大量请求,之前能过,只是因为从来没有东西被拦过。我把限额做成可配置的,这样 CI 可以用更高的数字。
我还加了两个测试,检查限流器本身,而不是接口:
- 发一个超过限额的请求,期望返回 429 Too Many Requests
- 通过同一个代理,以两个不同用户的身份发请求,期望得到两个独立的计数器
在这之前,我从没见过自己的限流器返回 429。我觉得那才是真正的警告,而我错过了。
检查你自己的应用
发出超过限额的请求数。当然,只对自己的应用发:
for i in $(seq 1 11); do curl -s -o /dev/null -w "%{http_code}\n" -X POST https://your-app/login done
最后你应该看到 429。如果只看到 200,你的限额没在工作。然后每次换一个伪造的 X-Forwarded-For 头再试。如果 429 消失了,任何人都能绕过你的限额。
你有没有在生产环境里验证过,你的限流器真的会返回 429?我很好奇,是不是只有我一个人没查过。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.