发布于5小时前5小时 背景在一次授权渗透测试中,目标架构为“公网负载均衡—Nginx—业务应用”。管理接口与普通业务共用域名,应用通过客户端 IP 判断请求是否来自内网:内网地址允许访问管理功能,公网地址返回 403。这类设计的风险点不在管理接口本身,而在客户端 IP 的获取链路。如果应用直接信任用户可控的 X-Forwarded-For、X-Real-IP 或 Forwarded,攻击者可能伪造内网地址绕过权限边界。以下验证应限定在授权目标和无副作用接口,例如健康检查、版本信息或只读状态页。不要使用删除、发布、重置密码等管理操作证明漏洞。问题定位测试时发现,正常访问 /admin/health 返回 403,但加入特定代理头后返回 200。响应差异表明管理接口的访问控制依赖了请求头中的地址,而不是经过可信代理校验的连接源地址。常见缺陷代码如下:from ipaddress import ip_address xff = request.headers.get('X-Forwarded-For') client_ip = xff.split(',')[0].strip() if xff else request.remote_addr if ip_address(client_ip).is_private or ip_address(client_ip).is_loopback: allow_admin_request() else: deny_request()这里存在三个问题:X-Forwarded-For 可以由公网客户端直接提交。代码固定取逗号分隔后的第一个地址,没有结合可信代理数量反向解析。使用 IP 地址代替身份认证,一旦代理配置或解析逻辑变化,权限边界随之失效。授权验证步骤先建立基线,只记录状态码、响应长度和跳转位置,避免拉取敏感内容:export TARGET=' curl -sk -o /dev/null -D - \ "$TARGET/admin/health" curl -sk -o /dev/null -D - \ -H 'X-Forwarded-For: 127.0.0.1' \ "$TARGET/admin/health" curl -sk -o /dev/null -D - \ -H 'X-Forwarded-For: 10.10.10.10' \ "$TARGET/admin/health" curl -sk -o /dev/null -D - \ -H 'X-Real-IP: 127.0.0.1' \ "$TARGET/admin/health" curl -sk -o /dev/null -D - \ -H 'Forwarded: for=127.0.0.1;proto=https' \ "$TARGET/admin/health"如果基线为 403,而伪造某个头后稳定变为 200,需要继续确认究竟是哪一层解析了该头。不要仅凭一次响应下结论,CDN 缓存、WAF 策略和灰度节点也可能造成差异。可以使用不同的地址顺序判断应用如何解析代理链:for value in \ '127.0.0.1, 198.51.100.20' \ '198.51.100.20, 127.0.0.1' \ '10.0.0.8, 198.51.100.20' \ '198.51.100.20, 10.0.0.8' do echo "--- X-Forwarded-For: $value" curl -sk -o /dev/null \ -w 'status=%{http_code} size=%{size_download} redirect=%{redirect_url}\n' \ -H "X-Forwarded-For: $value" \ "$TARGET/admin/health" done判断方式如下:只有第一个地址为内网时成功:应用大概率直接取左侧第一个地址。只有最后一个地址为内网时成功:应用可能取最右侧地址,但没有剥离可信代理。单独设置 X-Real-IP 生效:可能是 Nginx、网关或应用直接读取该字段。不同节点结果不一致:重点检查负载均衡后的配置漂移和不同版本中间件。代理配置排查Nginx 中较常见的问题是直接保留客户端提交的代理头:location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $http_x_real_ip; proxy_pass }$proxy_add_x_forwarded_for 会在原始 X-Forwarded-For 后追加连接地址。如果应用取最左侧地址,攻击者提交的内容仍然位于最前面。第二行的问题更直接:它把用户提交的 X-Real-IP 原样传给后端。排查时需要同时确认以下信息:公网入口是否覆盖客户端提交的代理头。后端是否可以绕过入口代理直接访问。Nginx set_real_ip_from 是否设置成了过大的网段。应用框架配置的可信代理数量是否与实际拓扑一致。IPv6、IPv4 映射地址和多个同名请求头是否采用一致的解析规则。尤其要避免下面这种配置:set_real_ip_from 0.0.0.0/0; set_real_ip_from ::/0; real_ip_header X-Forwarded-For;它等于允许任意连接方声明自己的真实地址。即使当前服务位于负载均衡后,也应填写负载均衡或入口代理的精确地址段,而不是全网段。日志取证与链路确认只记录最终解析出的客户端 IP 不足以复盘此类问题。建议同时保留 TCP 对端地址、原始代理头、解析后地址、上游节点和请求标识:log_format boundary '$time_iso8601 ' 'peer=$remote_addr ' 'original=$realip_remote_addr ' 'xff="$http_x_forwarded_for" ' 'xreal="$http_x_real_ip" ' 'request="$request" ' 'status=$status ' 'upstream=$upstream_addr ' 'request_id=$request_id'; access_log /var/log/nginx/access_boundary.log boundary;针对管理路径进行初步检索时,可以先关注公网连接来源与内网声明地址同时出现的记录:grep 'request="GET /admin/' /var/log/nginx/access_boundary.log \ | grep -E 'xff="(127\.0\.0\.1|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)'该命令只能用于缩小范围,不能直接认定为攻击。企业代理、服务网格和健康检查也可能产生内网地址。最终需要结合 peer、入口负载均衡日志、请求标识及认证日志进行关联。修复方案修复应从边界入口、代理链和应用鉴权三层同时处理。如果公网入口是唯一可信边界,可以在入口处覆盖外部提交的地址头:location / { proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Forwarded ''; proxy_pass }后续代理仅信任入口代理的精确地址,并将校验后的地址传给应用:set_real_ip_from 10.20.1.10/32; set_real_ip_from 10.20.1.11/32; real_ip_header X-Forwarded-For; real_ip_recursive on; location / { proxy_set_header X-Verified-Client-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass }这套配置成立的前提是应用端口只能被该代理访问。否则攻击者绕过代理直连应用时,仍然可以伪造 X-Verified-Client-IP。应通过安全组、防火墙、Kubernetes NetworkPolicy 或监听地址限制后端入口。应用层不应仅依靠内网 IP 授权。管理接口至少需要具备以下控制:独立身份认证,例如短时令牌、OIDC 或双向 TLS。基于角色和具体操作的授权,避免“来自内网即管理员”。对高风险操作增加重新认证、审批或双人复核。记录主体、来源、操作对象、结果和请求标识,避免只记录客户端 IP。管理平面与公网业务入口分离,无法分离时至少采用独立路由和明确的鉴权中间件。回归验证修复后应从公网入口、可信代理和后端直连三个位置分别测试:公网请求伪造任意代理头,结果均应保持未授权。合法用户通过入口访问时,审计日志应记录正确来源地址。非代理节点访问应用端口应在网络层被拒绝。管理接口在没有有效身份凭据时,即使来源为内网也不应获得管理权限。IPv4、IPv6、多值头和重复头的处理结果应保持一致。建议把代理头伪造用例加入发布后的自动化安全回归,尤其是在更换 CDN、负载均衡、服务网格或应用框架后重新验证。此类漏洞往往不是单点代码错误,而是代理拓扑变化后,原有可信边界假设失效。总结客户端 IP 本质上是经过代理链计算出的属性,而不是可以直接信任的请求字段。渗透验证的重点应放在地址头覆盖规则、可信代理范围、应用解析顺序、后端可达性和实际鉴权机制上。修复时仅修改一行取 IP 代码通常不够,必须同时收紧网络入口、统一代理链解析规则,并让管理权限回归到可审计的身份认证与授权体系。 网络请求、日志与边界流量分析示意 代码审计、调用链与关键函数定位示意
3小时前3小时 这类问题的根因通常不是“拿到了客户端 IP”,而是应用把可伪造的请求头当成了授权依据。尤其要注意:只限制管理接口来源 IP,但没有同时限定可信代理链路时,X-Forwarded-For、X-Real-IP 都可能被客户端直接注入。 建议按下面几处排查: 先确认应用实际读取哪个字段,以及是否无条件启用了 Proxy Trust: curl -i \ -H 'X-Forwarded-For: 127.0.0.1' \ -H 'X-Real-IP: 127.0.0.1' \ -H 'Forwarded: for=127.0.0.1' 同时在应用临时记录连接源地址、原始转发头和框架解析后的客户端地址。重点区分:TCP 对端地址是否确实是反向代理,以及框架最终选择了转发链中的哪一项。 在最外层代理覆盖客户端提交的头,不要继续透传同名值。Nginx 单层代理可以这样处理: location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Forwarded ""; proxy_pass } 如果确实存在多层代理,应该只信任明确的负载均衡器网段,再由 Real IP 模块重建地址: set_real_ip_from 10.20.0.0/16; set_real_ip_from 192.0.2.10; real_ip_header X-Forwarded-For; real_ip_recursive on; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; set_real_ip_from 0.0.0.0/0 或信任整个内网大段都比较危险,容器网络、SSRF 跳板和旁路入口经常会让这个边界失效。 应用侧不要用“取 XFF 第一项”这种固定规则。正确做法是从连接源地址开始,按反向代理链由右向左剥离已知可信代理;遇到第一个非可信地址即停止。以 Express 为例,不要直接设置: app.set('trust proxy', true); 应限定实际代理地址或跳数,地址白名单通常比固定跳数更稳: const proxyaddr = require('proxy-addr'); const trust = proxyaddr.compile([ 'loopback', '10.20.0.0/16', '192.0.2.10/32' ]); app.set('trust proxy', addr => trust(addr)); 补测容易漏掉的地址表示法:::1、::ffff:127.0.0.1、IPv6 压缩形式,以及带空格、多值、多请求头的 XFF。地址应先用标准 IP 库解析和规范化,再做 CIDR 判断,避免字符串前缀匹配: # 不安全示例 client_ip.startswith("127.") client_ip == "10.0.0.1" # 应使用标准 IP/CIDR 解析库判断网段归属 最后,管理接口不建议只依赖来源 IP 做授权。至少再加一层独立认证,并在网络层限制应用端口只能被反向代理访问;否则攻击者如果能绕过代理直连后端,即使转发头处理正确,也可能命中应用对 remote_addr 的错误信任。修复后应分别从公网入口、可信代理、后端直连路径执行回归测试,并确认伪造头不会影响最终授权结果。
创建帐户或登录后发表意见