在 Cloudflare 和 nginx 后面,我所有用户的 IP 都是一样的
发布时间:2026/10/8 21:49:42 锦皓数字建站

我的管理员登录页面限制每分钟最多输错 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它和 nginx 运行在同一台机器上。所以每个请求到达时都是这样的这里的用户 IP 只是示例X-Forwarded-For: 203.0.113.42, 127.0.0.1 ^^^^^^^^^^^^^^ ^^^^^^^^^ 真实用户 cloudflared由 nginx 追加 修复前我的应用使用的是127.0.0.1 修复后我的应用使用203.0.113.42默认情况下ASP.NET 的转发头中间件forwarded headers middleware只读取一个条目也就是最后一个。而最后一个永远是127.0.0.1。所以我的每个 IP 允许 3 次访客注册的限制本来会变成所有人共享 3 次访客注册。同一个地方还有第二个问题。我的配置还信任来自任何人的这个头字段。在生产环境中第一个问题掩盖了它但如果没有那多出来的一跳 nginx客户端可以在头字段里随便写一个 IP从而获得一个全新的计数器。我在测试环境里验证过确实可行。这是同时修复两个问题的方案稍微精简过builder.Services.ConfigureForwardedHeadersOptions(o { o.ForwardedHeaders ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; o.ForwardLimit null; // 读取整个列表而不只是最后一个条目 // 只信任我自己的跳数回环地址和私有网络 o.KnownIPNetworks.Clear(); o.KnownIPNetworks.Add(System.Net.IPNetwork.Parse(127.0.0.0/8)); o.KnownIPNetworks.Add(System.Net.IPNetwork.Parse(::1/128)); o.KnownIPNetworks.Add(System.Net.IPNetwork.Parse(10.0.0.0/8)); o.KnownIPNetworks.Add(System.Net.IPNetwork.Parse(172.16.0.0/12)); o.KnownIPNetworks.Add(System.Net.IPNetwork.Parse(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吗我很好奇是不是只有我一个人没检查过。相关阅读延伸外链以下为推荐的相关技术教程来自致知笔记如何将笔记本电脑连接到外接显示器如何检查 IP 地址是静态还是动态Static/Dynamic IP如何在 Windows 11/10 中创建密码重置盘
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。