发布于5小时前5小时 背景:同一个接口,为什么换个请求头就能访问?问题场景:在授权测试环境中,业务链路为“客户端 → 负载均衡 LB → Nginx → 应用”。管理接口按来源 IP 限制访问,普通测试出口请求返回 403,但添加 X-Forwarded-For 后却返回 200。应该检查 Nginx、应用代码,还是负载均衡配置?这类问题的关键不是“请求头能不能伪造”,而是:谁有权声明客户端地址,以及这份声明在哪一层参与了授权。下面以只读接口 /admin/health 为例,给出定位和复测方法。所有请求均应限定在书面授权的环境、域名和路径内。问题定位:先确认是谁放行了请求不要仅凭 200 判断权限绕过成立。登录页面、统一错误页面也可能返回 200。需要同时确认响应内容确实来自受限接口,以及请求是否到达应用。记录正常请求与修改请求的时间、状态码、响应内容特征和请求 ID。检查 Nginx 是否配置 allow/deny,以及应用是否另外解析了 X-Forwarded-For 或 X-Real-IP。检查 real_ip_header、set_real_ip_from、real_ip_recursive 的实际生效值。确认源站端口是否只允许 LB 访问,避免把“经过代理”和“直连源站”混为一谈。先查看实际加载的配置,而不是只检查某个配置文件。输出可能含内部域名、证书路径等信息,不要直接贴到公开帖子。sudo nginx -T 2>&1 | less # 快速定位相关配置,最终仍需结合所在的 http/server/location 上下文判断 sudo nginx -T 2>&1 | grep -nE \ 'real_ip_header|set_real_ip_from|real_ip_recursive|allow |deny |proxy_set_header|access_log' # 确认应用与代理的监听地址 sudo ss -lntp技术分析:两个常见的错误信任点第一类问题是应用直接取 X-Forwarded-For 的第一个地址作为客户端 IP,并据此执行管理接口白名单。若入口没有覆盖或规范化这个头,外部输入就变成了授权依据。第二类问题是 Nginx 过度信任代理来源,例如配置 set_real_ip_from 0.0.0.0/0。启用 real_ip 模块后,可信来源提供的地址会用于改写 $remote_addr;此时 allow/deny 可能依据被改写的地址执行。把所有来源都列为可信,相当于允许任意连接方参与声明自己的身份。还有一种容易遗漏的情况:Nginx 已正确恢复客户端地址,但应用仍自行解析原始 X-Forwarded-For,导致两层使用不同的 IP 判断权限。来源 IP 不是从请求头里“读取出来”就可信了。可信客户端地址应由受控代理链计算,并通过明确约定传给下一层。操作步骤:用最小证据区分故障位置先在测试环境增加结构化访问日志。以下 log_format 放在 http 上下文,access_log 放入对应 server。$realip_remote_addr 用于记录地址改写前的连接对端,$remote_addr 则是当前生效地址;该变量依赖 real_ip 模块支持。log_format ip_boundary escape=json '{"time":"$time_iso8601",' '"rid":"$request_id",' '"peer":"$realip_remote_addr",' '"client":"$remote_addr",' '"xff":"$http_x_forwarded_for",' '"path":"$uri",' '"status":"$status",' '"upstream_status":"$upstream_status"}'; # 放入目标 server access_log /var/log/nginx/ip_boundary.log ip_boundary;从一个不在管理白名单内、但被授权用于验证的出口发起以下请求。示例地址属于文档保留地址,必须替换为测试环境实际配置的白名单地址;不要为了复测将测试出口加入白名单。# 基线:预期被管理接口的来源限制拒绝 curl --max-time 10 -sS -D - \ # 仅改变一个变量:客户端提交的来源声明 curl --max-time 10 -sS -D - \ -H 'X-Forwarded-For: 198.51.100.23' \ # 单独检查应用是否错误信任另一个头 curl --max-time 10 -sS -D - \ -H 'X-Real-IP: 198.51.100.23' \ # 在对应时间窗口核对日志 sudo tail -n 50 /var/log/nginx/ip_boundary.log若修改头后 client 变成白名单地址,优先检查 Nginx 的代理信任配置,以及 LB 实际如何处理外部传入的头。若 client 不变,但应用从拒绝变成允许,优先检查应用自己的客户端 IP 提取逻辑。若 status 为 403 且 upstream_status 为“-”,通常说明请求没有进入上游;仍应结合 location 和错误处理配置确认。若 upstream_status 有值,则检查应用日志,确认应用实际看到的连接来源和授权依据。关键配置:收紧信任来源,并统一传递地址下面适用于一个明确的简化拓扑:只有 LB 能连接该 Nginx,LB 的对端地址是 192.0.2.10,LB 会可靠记录真实客户端地址,应用只接受本机 Nginx 的连接。配置片段应合并到现有配置,不能脱离实际网络直接照搬。# 放入目标 server 或合适的 http 上下文 # 这里只信任实际连接 Nginx 的 LB 地址 set_real_ip_from 192.0.2.10/32; real_ip_header X-Forwarded-For; real_ip_recursive on; # 以下代理头统一放在 server 上下文 proxy_set_header Host $host; proxy_set_header X-Request-ID $request_id; proxy_set_header X-Real-IP $remote_addr; # 对只经此 Nginx 访问的应用,传递单一、已规范化的客户端地址 proxy_set_header X-Forwarded-For $remote_addr; location /admin/ { allow 198.51.100.0/24; deny all; proxy_pass } location / { proxy_pass }这里有三个前提需要核实。第一,LB 必须覆盖外部声明,或者以明确、正确的规则追加实际客户端地址;多级代理场景要逐跳确认,而不是简单信任整个内网网段。第二,应用必须只信任受控代理传来的规范化地址,不能继续随意选取其他来源头。第三,如果某个 location 自己定义了 proxy_set_header,需要检查继承行为,避免上述头配置在那里失效。如果后端部署在独立主机或容器网络中,应将示例中的回环地址替换为实际服务地址,并通过安全组或防火墙限制只有 Nginx 可以连接。单靠“后端地址没有公开”不构成访问控制。# 变更前先校验,校验通过后再按变更流程重载 sudo nginx -t && sudo systemctl reload nginx风险与修复验收修复后,从非白名单出口分别执行基线、伪造 X-Forwarded-For、伪造 X-Real-IP 请求,结果应保持拒绝,日志中的有效客户端地址不应被这些声明改变。从合法管理出口复测,确认业务没有被误封。若还有登录认证,应使用测试账号验证完整授权链,而不是只看网络白名单。检查实际存在的管理路由及其匹配关系。示例中的 /admin/ 不自动涵盖其他前缀,也不能代替对 /admin 路由行为的检查。在明确授权下确认源站和应用端口不能绕过入口直接访问。预期应由网络策略阻断非预期来源。保留变更前后配置、请求 ID、日志和响应差异作为证据;原始来源头属于不可信输入,日志展示时仍需转义并控制访问权限。管理接口不能只靠 IP 白名单保护。应同时保留身份认证、角色授权和敏感操作审计;必要时通过专用管理入口或双向 TLS 增强身份约束。总结这类问题的排查顺序是:先确认受限功能是否真的可访问,再沿着“连接对端 → Nginx 生效地址 → 转发头 → 应用授权依据”逐层核对。修复目标不是删除某个请求头,而是确保外部输入无法越过代理信任边界,并让每一层都使用同一份经过验证的客户端地址。 网络请求、日志与边界流量分析示意 服务器、云资产与权限边界梳理示意
4小时前4小时 这个现象通常不是“伪造 IP”本身的问题,而是应用把客户端可控的 X-Forwarded-For 当成了鉴权依据。建议先把请求链路画清楚:客户端 → CDN/WAF → LB/Ingress → 应用,确认究竟由哪一层生成、覆盖和解析该头。 可以先做一组差异测试,并同时查看每一层访问日志: curl -i curl -i -H 'X-Forwarded-For: 127.0.0.1' curl -i -H 'X-Forwarded-For: 10.0.0.8' curl -i -H 'X-Forwarded-For: 203.0.113.10, 127.0.0.1' curl -i -H 'Forwarded: for=127.0.0.1' 日志里至少同时记录连接源地址、原始转发头和框架解析后的客户端地址,例如 Nginx 可临时使用: log_format ip_debug '$remote_addr ' 'xff="$http_x_forwarded_for" ' 'realip="$realip_remote_addr" ' 'request="$request" status=$status'; 重点确认以下几件事: 公网入口是否无条件覆盖客户端传入的 X-Forwarded-For,还是直接透传/追加。 应用是否能绕过代理直接访问;如果源站端口对测试网络或公网开放,攻击者可自行构造完整转发链。 框架的“信任代理”是否配置成了全信任,例如 Express 的 trust proxy = true,或 Nginx 的 set_real_ip_from 0.0.0.0/0。 管理接口是否仅依据“内网 IP/本机 IP”放行。IP 最多适合作为附加限制,不应替代登录、会话或服务身份认证。 Nginx 的修复思路是只信任实际反向代理网段,并在入口清洗用户传入的头。后端 Nginx 示例: # 只填写实际 LB/Ingress 的地址段,不能写 0.0.0.0/0 set_real_ip_from 10.20.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_pass } 如果这是最外层、直接接收不可信客户端流量的代理,建议不要基于客户端已有的 XFF 继续追加,而是覆盖: proxy_set_header X-Forwarded-For $remote_addr; Express 等框架也应显式限定受信任代理,而不是按任意头取第一个地址: app.set('trust proxy', ['loopback', '10.20.0.0/16']); app.get('/admin', requireAuthenticatedAdmin, (req, res) => { // req.ip 只能用于审计或附加访问控制 res.sendStatus(204); }); 解析时应从连接源地址开始,按转发链从右向左剥离“已知可信代理”,第一个不可信地址才是客户端地址;直接取 XFF 最左项或用字符串前缀判断内网段都容易出问题。IP 网段判断应使用标准 CIDR 库,避免类似 startsWith("10.") 的实现。 修复后的验证不要只重放单个 PoC,建议覆盖以下矩阵: 外部未认证请求,无 XFF:拒绝。 外部未认证请求,伪造单个或多个 XFF/Forwarded:仍拒绝。 通过正常代理访问,日志中的客户端 IP 正确。 直接访问源站 IP/端口:网络层不可达,或同样要求管理身份认证。 合法管理员从允许网络访问:认证和授权通过。 普通已登录用户即使位于允许网段:仍因角色不足被拒绝。 最后建议把管理接口的根本控制落在认证与角色授权上,再叠加源站安全组、mTLS/VPN 或管理网段限制。这样即使后续代理链调整导致真实 IP 解析异常,也不会直接变成管理权限绕过。
创建帐户或登录后发表意见