发布于2小时前2小时 背景在一次授权渗透测试中,目标系统通过公网 Nginx 将请求转发到内网管理服务。管理接口没有独立的强身份认证,而是根据客户端 IP 判断是否来自办公网:公网入口:edge.example.test:443后端服务:10.20.4.17:8080允许网段:10.20.0.0/16受限路径:/ops/status、/ops/export初始访问 /ops/status 返回 403,但在请求中加入伪造的 X-Forwarded-For 后返回 200。问题不在单个组件,而在代理与应用对“真实客户端 IP”的理解不一致。以下验证应限定在书面授权范围内,优先使用只读状态接口,不调用导出、重启、任务执行等有副作用的功能。问题定位常见的错误链路如下:边缘 Nginx 使用 $proxy_add_x_forwarded_for,保留用户提交的 X-Forwarded-For。后端应用直接取 X-Forwarded-For 中最左侧地址作为客户端 IP。应用以该地址是否属于 10.20.0.0/16 作为管理权限依据。假设外部测试地址为 198.51.100.24,攻击者提交 X-Forwarded-For: 10.20.8.8。Nginx 转发后,后端实际收到的请求头可能变为:X-Forwarded-For: 10.20.8.8, 198.51.100.24如果后端取第一个地址,就会把外部请求识别为内网请求。这里需要区分两个问题:一是代理是否允许客户端污染转发头,二是应用是否只信任来自指定代理节点的转发信息。授权验证步骤先生成唯一测试标识,便于在边缘代理、应用和审计平台中关联请求:RID="pt-xff-$(date +%Y%m%d%H%M%S)" # 基线请求,预期为 401 或 403 curl -sk -o /dev/null -D - \ ' \ -H "X-Pentest-ID: $RID-baseline" # 伪造内网来源,仍只访问无副作用的状态接口 curl -sk -o /dev/null -D - \ ' \ -H 'X-Forwarded-For: 10.20.8.8' \ -H "X-Pentest-ID: $RID-spoof"判定时不要只看 HTTP 状态码,还应对比响应长度、认证跳转和业务错误码。推荐记录以下证据:基线请求返回 403,而伪造请求返回 200。两次请求使用相同方法、路径和认证状态,唯一有效差异是转发头。边缘日志中的 TCP 来源仍为外部测试地址。应用日志中的客户端地址被记录为 10.20.8.8。如果接口支持 HEAD,可优先使用 HEAD。若应用对 HEAD 和 GET 的鉴权逻辑不同,则使用文档确认无副作用的 GET 接口。日志关联与排查Nginx 建议同时记录连接源地址、用户提交的转发头、请求标识和上游状态。只记录最终解析后的客户端 IP,会丢失信任链证据。log_format pentest escape=json '{' '"time":"$time_iso8601",' '"request_id":"$request_id",' '"pentest_id":"$http_x_pentest_id",' '"peer_ip":"$remote_addr",' '"xff":"$http_x_forwarded_for",' '"request":"$request",' '"status":$status,' '"upstream_status":"$upstream_status"' '}'; access_log /var/log/nginx/access_pentest.json pentest;在代理节点和应用节点上按测试标识检索:grep -F "$RID" /var/log/nginx/access_pentest.json journalctl -u admin-api \ --since '10 minutes ago' \ --no-pager | grep -F "$RID" # 检查外部连接携带私网 XFF 且成功访问的记录 jq -c 'select( (.peer_ip | startswith("10.") | not) and (.xff | test("(^|, )[ ]*(10\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.)")) and (.status >= 200 and .status < 300) )' /var/log/nginx/access_pentest.json上述查询只适合初筛。生产环境还需加入真实 CDN、WAF、负载均衡器的出口地址范围,避免把合法代理链误判为伪造请求。关键配置分析单层公网代理中,以下配置存在风险:# 风险配置:会把客户端提交的 X-Forwarded-For 保留在链首 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr;$proxy_add_x_forwarded_for 的行为是:已有 X-Forwarded-For 时在其末尾追加 $remote_addr。该写法本身并非始终错误,但前提是后端必须按可信代理链从右向左解析,并且明确知道可信代理数量或地址范围。后端简单取最左侧值时,该配置就会形成可利用边界。如果公网 Nginx 是唯一入口,可以在边界处丢弃客户端提交的转发信息,重新建立信任链:location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Request-ID $request_id; # 不使用客户端自行构造的标准 Forwarded 头 proxy_set_header Forwarded ""; proxy_pass }如果 Nginx 前方还有 CDN 或云负载均衡器,不能直接相信任意来源提交的 X-Forwarded-For。应只允许已知代理网段影响真实地址解析:# 示例地址必须替换为实际 CDN 或负载均衡器出口网段 set_real_ip_from 192.0.2.0/24; set_real_ip_from 2001:db8:100::/48; real_ip_header X-Forwarded-For; real_ip_recursive on; location / { # realip 模块完成校验后,$remote_addr 才作为下游可信来源 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_pass }同时应通过安全组或主机防火墙限制后端端口,只接受代理节点连接。否则攻击者可能绕过边缘代理,直接向后端提交伪造头。# 在后端节点检查实际监听范围 ss -lntp | grep ':8080' # 示例:仅允许代理节点 10.20.1.10 访问后端服务 iptables -A INPUT -p tcp -s 10.20.1.10 --dport 8080 -j ACCEPT iptables -A INPUT -p tcp --dport 8080 -j DROP应用侧权限边界修复不能只停留在 Nginx。应用侧需要明确“哪些代理可信”,而不是无条件读取任意请求头。若框架提供可信代理配置,应使用地址或网段白名单,不要自行按逗号分割后固定取第一项。以 Express 为例,可以限定只有本机和指定代理网段有权提供转发信息:const express = require('express'); const app = express(); app.set('trust proxy', [ 'loopback', '10.20.1.0/24' ]); app.get('/ops/status', requireLogin, requireRole('ops-read'), (req, res) => { res.json({ status: 'ok', clientIp: req.ip }); });即使真实 IP 解析正确,也不建议把内网地址作为唯一授权条件。管理接口至少还应具备登录态、角色检查和必要的二次认证。IP 白名单更适合作为附加限制,而不是身份凭证。风险评估若仅能访问只读状态接口,通常属于权限边界绕过和信息泄露。若可进入导出、配置修改或任务执行接口,风险会提升到敏感数据泄露或远程代码执行链路。若后端接口在内网复用相同 IP 鉴权逻辑,可能形成公网入口到多个内部管理服务的横向访问链。若日志只记录解析后的伪造地址,事件响应时可能错误归因,并掩盖真实连接来源。修复后的回归验证修复后应重复基线与伪造请求,并验证三层结果:外部请求无论提交何种 X-Forwarded-For,受限接口均返回 401 或 403。边缘日志保留真实 TCP 来源和原始可疑请求头。后端看到的客户端地址由可信代理重新生成,且接口仍要求有效身份和角色。for ip in '10.20.8.8' '127.0.0.1' '192.168.1.10' '203.0.113.9'; do printf '%-16s ' "$ip" curl -sk -o /dev/null -w '%{http_code}\n' \ ' \ -H "X-Forwarded-For: $ip" \ -H "X-Pentest-ID: regression-$ip" done预期结果应保持一致,不因客户端自行填写的来源地址发生权限变化。还需要从允许的办公网或测试跳板机执行一次合法访问,确认修复没有破坏正常代理链。总结这类问题的根因不是单纯的“可伪造请求头”,而是代理、应用和网络访问控制之间缺少统一的信任边界。渗透测试时应通过低影响请求验证权限差异,并利用请求标识关联各层日志。修复时需要同时处理边缘头部重建、可信代理范围、后端端口隔离和应用级强认证,避免只改一处后留下新的绕过路径。 网络请求、日志与边界流量分析示意 服务器、云资产与权限边界梳理示意
2小时前2小时 这个问题的关键通常不是“是否读取 X-Forwarded-For”,而是应用是否只信任真实反向代理,并按正确方向解析地址链。若客户端可以直连应用,或边缘代理保留了客户端传入的同名头,白名单就可能被伪造。 建议先确认每一跳实际收到的内容,不要只看应用最终记录的 IP。可临时记录: log_format ip_chain '$remote_addr | $http_x_forwarded_for | $realip_remote_addr'; access_log /var/log/nginx/ip-chain.log ip_chain; Nginx 边缘层应覆盖而不是透传客户端提供的转发头: location / { proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_pass } 如果存在多级代理,可由受控代理追加地址,但应用必须明确配置可信代理网段,不能信任任意来源: # 应用前置 Nginx,仅信任负载均衡器网段 set_real_ip_from 10.20.0.0/16; set_real_ip_from 192.168.50.10; real_ip_header X-Forwarded-For; real_ip_recursive on; 同时用防火墙或安全组限制应用端口只允许代理访问,否则攻击者绕过代理直连后,可信头配置仍可能失效: # 示例:仅允许反向代理 10.20.1.8 访问应用 8080 iptables -A INPUT -p tcp -s 10.20.1.8 --dport 8080 -j ACCEPT iptables -A INPUT -p tcp --dport 8080 -j DROP 验证时至少覆盖三组请求:伪造单个内网地址、构造多段地址链、直接访问后端端口: curl -H 'X-Forwarded-For: 127.0.0.1' curl -H 'X-Forwarded-For: 198.51.100.7, 10.0.0.8' curl -H 'X-Real-IP: 10.0.0.8' 修复后应以“最后一个非可信代理地址”作为客户端地址,并确认 IPv4、IPv6、带端口格式及重复请求头的处理一致。更稳妥的做法是不要仅凭来源 IP 保护管理接口,至少再叠加身份认证或双向 TLS。
创建帐户或登录后发表意见