发布于3小时前3小时 背景在授权渗透测试中,经常遇到通过客户端 IP 限制管理接口的场景,例如仅允许办公网或 VPN 网段访问 /admin、/metrics、/actuator 等路径。实际链路通常不是客户端直接访问应用,而是经过 CDN、WAF、Nginx 或负载均衡器:客户端 -> CDN/WAF -> Nginx -> 应用服务如果代理层没有覆盖外部传入的 X-Forwarded-For、X-Real-IP,或者源站错误地信任任意地址提交的转发头,攻击者就可能伪造来源 IP。此类问题不只是访问控制绕过,还会影响限速、审计、封禁和告警。以下测试应限定在书面授权资产内,优先选择只读健康检查接口,不执行配置修改、账号操作或数据导出。问题定位先明确真实流量路径和每一跳的职责,重点确认以下问题:公网入口是否允许客户端直接提交 X-Forwarded-For 或 X-Real-IP。源站是否配置了 set_real_ip_from 0.0.0.0/0,或信任范围明显大于实际代理网段。应用使用的是连接源地址,还是框架解析后的 req.ip、RemoteAddr。CDN 后面的源站是否仍能被公网直接访问。管理接口是否只依赖 IP 白名单,没有额外身份认证。有服务器权限时,可先检查 Nginx 是否启用了 realip 模块,并展开全部配置。nginx -T 的结果可能包含内部域名和证书路径,取证文件应按敏感配置管理。nginx -V 2>&1 | grep -o -- '--with-http_realip_module' nginx -T 2>&1 | grep -nE 'set_real_ip_from|real_ip_header|real_ip_recursive|allow |deny |proxy_set_header|location .*(admin|metrics|actuator)' # 同时检查是否存在重复配置或 include 文件覆盖 nginx -T 2>&1 | grep -nE '^# configuration file|set_real_ip_from|real_ip_header'以下配置是常见风险点:set_real_ip_from 0.0.0.0/0; real_ip_header X-Real-IP; location /admin/ { allow 127.0.0.1; allow 10.30.0.0/16; deny all; proxy_pass }realip 模块可能先根据请求头重写 remote_addr,随后 allow/deny 再基于重写后的地址判断。此时来自公网的请求只要能控制 X-Real-IP,就可能被识别为本机或可信网段。低影响漏洞验证建议选择 /admin/health、/metrics 的空查询或其他只读路径,先记录基线响应,再分别测试常见代理头。下面使用文档保留地址 192.0.2.10 作为示例;-k 仅适用于测试环境的自签名证书,正式验证应校验证书。HOST=admin.lab.example IP=192.0.2.10 PATHNAME=/admin/health # 基线请求 curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \ -w 'baseline status=%{http_code} size=%{size_download}\n' \ " # 测试 X-Real-IP 是否被直接信任 curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \ -H 'X-Real-IP: 127.0.0.1' \ -w 'x-real-ip status=%{http_code} size=%{size_download}\n' \ " # 测试 X-Forwarded-For 信任链 curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \ -H 'X-Forwarded-For: 127.0.0.1' \ -w 'xff status=%{http_code} size=%{size_download}\n' \ " 403,而加入某个头后变成 200,且响应内容确实来自受限接口,可初步确认信任边界存在问题。不要只看状态码:部分 WAF、登录页和后端错误页也可能返回 200,应同时比较响应头、正文长度和稳定特征。如果单地址未出现差异,还需要结合实际代理实现检查多跳解析规则。不同组件可能取最左侧、最右侧或最后一个非可信地址,不能仅凭一组请求判定安全。for value in \ '127.0.0.1, 198.51.100.25' \ '198.51.100.25, 127.0.0.1' \ '10.30.1.20, 198.51.100.25' do curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \ -H "X-Forwarded-For: $value" \ -w "xff=$value status=%{http_code} size=%{size_download}\n" \ " done这里只验证地址解析差异,不应继续访问敏感业务接口。若 WAF、CDN 与源站结果不一致,还应分别确认正常入口和源站直连路径,避免把 CDN 行为误判为源站漏洞。技术分析该问题通常由多个配置缺陷叠加形成:公网边缘代理使用 proxy_add_x_forwarded_for,把客户端自带的 X-Forwarded-For 原样保留并继续追加。后端应用错误地信任转发链最左侧地址,认为它一定是原始客户端。Nginx 使用过宽的 set_real_ip_from,导致互联网客户端也被视为可信代理。源站安全组允许公网直连,使攻击者可以绕过负责清洗请求头的 CDN 或 WAF。管理接口把 IP 白名单当作唯一认证条件。特别要区分“连接来源”和“业务判定地址”。Nginx 启用 realip 后,remote_addr 可能已经被替换,而 realip_remote_addr 通常保留建立 TCP 连接的原始地址。只记录 remote_addr 会导致日志看起来像请求真的来自 127.0.0.1 或办公网,给事件调查造成干扰。日志留痕与排查建议同时记录连接源地址、重写后的有效地址和原始转发头。示例日志格式如下:log_format trust_chain '$time_iso8601 conn=$realip_remote_addr effective=$remote_addr ' 'xff="$http_x_forwarded_for" xri="$http_x_real_ip" ' 'host="$host" request="$request" status=$status ' 'upstream="$upstream_addr" request_id="$request_id"'; access_log /var/log/nginx/access_trust_chain.log trust_chain;修改前先执行 nginx -t,确认配置正确后再平滑重载:nginx -t && nginx -s reload # 查找连接源不是本机,但有效地址被改写为本机的管理接口请求 grep 'request="GET /admin/' /var/log/nginx/access_trust_chain.log \ | grep 'effective=127.0.0.1' \ | grep -v 'conn=127.0.0.1' # 检查外部请求提交的转发头及状态变化 grep -E 'xff="[^"]+"|xri="[^"]+"' \ /var/log/nginx/access_trust_chain.log \ | grep -E 'request="(GET|HEAD) /(admin|metrics|actuator)'排查时应关联 WAF、负载均衡、Nginx 和应用日志,优先使用 request_id、时间戳、源端口及上游地址进行匹配。仅靠 IP 关联,在 NAT 或高并发代理环境中容易误判。修复建议第一步是收紧可信代理范围。set_real_ip_from 只能填写实际代理节点或专用网段,不能为了省事配置为全网:# 示例:仅信任内部负载均衡网段 set_real_ip_from 10.20.1.0/24; real_ip_header X-Forwarded-For; real_ip_recursive on;real_ip_recursive 的行为依赖可信代理列表。启用前应确认代理顺序,并用真实流量回归测试;它不能弥补 set_real_ip_from 范围过宽的问题。第二步是在公网第一跳覆盖客户端提交的转发头。如果该 Nginx 就是直接面向互联网的边缘节点,可明确重置:location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass }如果前面还有 CDN,应按 CDN 文档使用其固定客户端地址头,并同步官方出口网段;同时通过安全组或防火墙限制源站只接受 CDN/WAF 节点访问。否则攻击者可以绕过边缘代理直接访问源站,并自行构造所有转发头。第三步是收紧应用框架的可信代理设置。例如 Express 不应直接设置 trust proxy 为 true,而应限定实际代理:app.set('trust proxy', ['loopback', '10.20.1.0/24']);第四步是调整权限边界。IP 白名单适合做附加控制,不适合作为唯一身份认证。管理接口至少应叠加 VPN、mTLS、OIDC 或独立的反向代理认证,并将只读监控接口与可执行管理操作分离。管理路径使用独立域名和监听端口,避免与公网业务共用规则。源站禁止公网直连,只允许指定负载均衡或堡垒机网段。高风险接口同时校验用户身份、角色和请求来源。限速与封禁同时参考连接源地址和经过验证的客户端地址。保留原始转发头,但不要直接用未经验证的头作为授权依据。修复验证修复后应重复基线和伪造头测试,预期结果是外部请求无论提交何种 X-Real-IP、X-Forwarded-For,权限判定都保持一致;只有真实经过 VPN 或可信代理链的请求才能访问。# 检查配置并重载 nginx -t && nginx -s reload # 伪造头应继续被拒绝 curl --resolve "$HOST:443:$IP" -sS -k -o /dev/null \ -H 'X-Real-IP: 127.0.0.1' \ -H 'X-Forwarded-For: 127.0.0.1' \ -w 'post-fix status=%{http_code}\n' \ " 是代理节点地址,effective 是真实客户端地址;直连源站请求则应在网络层被拒绝,而不是仅依赖应用返回 403。总结客户端 IP 信任链问题的核心不是某个请求头本身,而是代理边界定义错误。有效的渗透验证应同时覆盖入口头部处理、realip 配置、应用框架解析、源站直连路径和日志真实性。修复时应遵循“边缘覆盖外部输入、内部只信任明确代理、源站限制直连、管理接口叠加身份认证”的原则,并通过前后对照请求和多层日志完成闭环。 网络请求、日志与边界流量分析示意 侦察、入口、横向移动与权限路径梳理示意
3小时前3小时 这类问题验证时,重点不是“能不能伪造 X-Forwarded-For”,而是确认伪造值在哪一跳被采信,以及攻击者能否绕过最外层代理直接访问源站。 建议按下面顺序排查: 分别测试入口代理和源站地址。在授权环境中发送自定义请求头,对比应用识别出的客户端 IP: curl -k -i \ -H 'X-Forwarded-For: 127.0.0.1, 10.0.0.8' \ -H 'X-Real-IP: 127.0.0.1' curl -i \ -H 'Host: example.com' \ -H 'X-Forwarded-For: 127.0.0.1' 如果经过正常入口时仍能控制应用看到的 IP,通常是边缘代理沿用了客户端提供的头;如果只有直连源站时可控,则主要问题是源站暴露或安全组配置不严。 同时记录传输层来源和解析后的来源。Nginx 启用 realip 模块后,$remote_addr 可能已被替换,单独记录它不利于追溯。可以临时增加: log_format ip_chain escape=json '{"peer":"$realip_remote_addr",' '"client":"$remote_addr",' '"xff":"$http_x_forwarded_for",' '"xri":"$http_x_real_ip",' '"host":"$host","request":"$request",' '"status":$status,"request_id":"$request_id"}'; access_log /var/log/nginx/ip_chain.log ip_chain; 这里的 peer 用于确认实际连接方,client 是 Nginx 计算后的地址,原始转发头用于复盘信任链。应用日志也应带上同一个 request ID,避免只看某一层日志得出误判。 核对每一跳的处理规则。最外层、直接面向互联网的代理应覆盖用户传入的转发头,而不是无条件追加: proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Request-ID $request_id; 后续内部代理才适合在已建立的可信链上追加: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 如果边缘层直接使用 $proxy_add_x_forwarded_for,客户端预先放入的值会被保留下来,后端按“最左侧 IP”取值时就容易被绕过。 可信代理必须使用明确网段,不要信任任意来源: set_real_ip_from 10.20.0.0/16; set_real_ip_from 192.168.50.10/32; real_ip_header X-Forwarded-For; real_ip_recursive on; 不要配置 set_real_ip_from 0.0.0.0/0 或 ::/0。多级代理场景下,Nginx 会从右向左跳过可信代理,取第一个非可信地址,所以代理网段缺失或配置过宽都会改变最终结果。 应用层不要再次用另一套规则解析。例如 Express 不应简单设置: app.set('trust proxy', true); 更稳妥的是限制为实际代理网段: const proxyaddr = require('proxy-addr'); const trusted = proxyaddr.compile([ '10.20.0.0/16', '192.168.50.10/32' ]); app.set('trust proxy', ip => trusted(ip)); 固定“信任 1 跳/2 跳”也要谨慎:如果不同请求存在长短不同的代理路径,攻击者可能从较短路径补齐伪造地址。 修复上还应配合网络层限制:源站端口只允许负载均衡器或反向代理网段访问,管理接口不要仅依赖客户端 IP 白名单。可以从源站检查当前监听和防火墙规则: ss -lntp nft list ruleset iptables -S ip6tables -S 如果使用 PROXY protocol,也要确保监听端口只能被可信代理访问;PROXY protocol 本身不提供身份认证,公网可达时同样可以伪造源地址。 回归验证至少覆盖三种情况:正常用户经过完整代理链、携带伪造转发头经过入口、绕过入口直连源站。最终应满足:外部自带头不会影响识别结果,源站无法从非代理网段访问,日志中能同时还原 socket peer、原始头和最终解析 IP。
创建帐户或登录后发表意见