发布于8小时前8小时 网站接入 CDN、WAF 或 Nginx 反向代理后,应用看到的连接来源通常变成代理节点 IP。为了恢复访客地址,很多项目会直接读取 X-Forwarded-For,并将其用于后台访问控制、登录限速或审计。这类配置一旦没有限定可信代理,攻击者就可能伪造来源 IP;如果源站还能被直接访问,还会进一步绕过 CDN 上的防护策略。下面以已授权的测试环境为例,说明如何验证代理头信任问题、确认源站暴露范围,并完成日志和配置修复。示例中的域名、地址均为测试占位符。背景场景测试站点的链路如下:访问者 -> CDN/WAF -> Nginx 源站 -> Web 应用 公网域名: 源站域名:origin.example.test 源站地址:192.0.2.10 内部管理路径:/ops/管理路径原计划只允许办公网访问,登录接口则按照客户端 IP 做限速。初步检查发现,应用直接使用 X-Forwarded-For 的第一个地址作为客户端 IP,而 Nginx 没有限制该头只能由 CDN 节点提供。问题定位先从正常入口建立基线,记录状态码、响应头和请求耗时。测试时不要只看页面内容,302、401、403、429 的变化通常更有判断价值。curl -sk -D- -o /dev/null curl -sk -D- -o /dev/null然后在授权环境中分别提交常见代理头,观察管理路径或限速结果是否发生变化:curl -sk -D- \ -H 'X-Forwarded-For: 127.0.0.1' \ -o /dev/null curl -sk -D- \ -H 'X-Real-IP: 10.10.20.15' \ -o /dev/null curl -sk -D- \ -H 'Forwarded: for=127.0.0.1' \ -o /dev/null如果不带头时返回 403,带入伪造地址后变成 200 或 302,说明访问控制依赖了可控请求头。即使页面仍要求登录,也应视为权限边界缺陷,因为 IP 白名单这一层已经失效。对于登录限速,可以在专用测试账号和约定频率下验证:连续请求触发 429 后,仅修改 X-Forwarded-For 是否能恢复请求。不要在生产账号上做高频测试,避免锁号和污染业务指标。检查源站是否可绕过 CDN源站地址应由资产清单、云平台控制台或运维侧提供,不建议在未约定范围内做外部枚举。拿到测试源站地址后,可通过 curl 的 resolve 参数保持正确的 TLS SNI 和 Host:curl -sk --resolve \ -D- \ -o /dev/null若该请求能直接到达业务,并且响应中缺少 CDN 注入的请求标识,基本可以确认源站公网可达。此时还需要比较两条路径的安全策略:CDN 路径是否有 WAF、Bot 管理、限速或地域规则。源站直连是否执行相同的认证和访问控制。源站证书是否接受公网域名,是否存在默认虚拟主机暴露。源站是否把任意直连请求误认为来自可信代理。技术分析:问题通常出在三层网络层:安全组或防火墙允许任意公网地址访问源站 80、443 端口。代理层:Nginx 无条件接受 X-Forwarded-For、X-Real-IP 等客户端可控请求头。应用层:程序自行取 X-Forwarded-For 的第一个值,并据此执行白名单、限速或审计。X-Forwarded-For 本身不是可信身份。它可能包含多个逗号分隔的地址,只有当请求确实来自受控代理,并且入口代理会覆盖或规范化该头时,才能作为客户端地址来源。尤其要避免“只要存在该头就信任”的实现。Nginx 日志留痕排查阶段建议同时记录 TCP 对端、realip 模块解析后的地址、原始代理头和请求标识。这样可以区分 CDN 节点、真实访客以及直接访问源站的请求。log_format edge '$time_iso8601 ' 'peer=$realip_remote_addr client=$remote_addr ' 'xff="$http_x_forwarded_for" ' 'request_id=$request_id host="$host" ' 'request="$request" status=$status bytes=$body_bytes_sent'; access_log /var/log/nginx/edge_access.log edge; proxy_set_header X-Request-ID $request_id; proxy_set_header Host $host;其中,启用 realip 模块后,remote_addr 是解析后的客户端地址,realip_remote_addr 保留原始连接对端。应用日志也应记录同一个 X-Request-ID,并附带认证主体、路由、结果和限速命中情况,避免只能看到 IP 却无法串联上下游请求。可用以下命令快速筛选伪造头、源站直连和管理路径访问记录:grep 'request="GET /ops/' /var/log/nginx/edge_access.log grep 'xff="127.0.0.1' /var/log/nginx/edge_access.log awk '$0 ~ /status=200/ && $0 ~ /\/ops\// {print}' \ /var/log/nginx/edge_access.log可信代理配置如果使用 CDN 提供的专用客户端地址头,应只信任官方代理网段,并确认 CDN 会覆盖该头,而不是透传访客提交的同名头。以下地址仅为示例,实际配置必须使用服务商当前公布的完整网段:# 仅示例:实际网段应由自动化任务同步并经过变更审核 set_real_ip_from 203.0.113.0/24; set_real_ip_from 2001:db8:100::/48; real_ip_header X-CDN-Client-IP; real_ip_recursive on;如果入口代理完全由自己控制,建议在公网入口覆盖外部传入的 X-Forwarded-For,而不是直接继承:# 公网入口层:丢弃客户端伪造链,仅写入连接来源 proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Request-ID $request_id;在后续受控代理层才考虑追加地址。多层代理环境中,应明确每一跳由谁写入、是否覆盖、应用信任几跳,不能简单地取最左侧地址。源站访问控制最有效的修复是让源站只接受 CDN、负载均衡器或运维网络的连接。优先在云安全组或上游防火墙实施,因为请求在到达 Nginx 前就会被拒绝。下面的 nftables 片段仅展示规则意图,部署前应通过控制台保留管理通道并验证 IPv6 策略:table inet origin_filter { set cdn_v4 { type ipv4_addr flags interval elements = { 203.0.113.0/24 } } chain input { type filter hook input priority 0 policy drop ct state established,related accept iifname lo accept ip saddr @cdn_v4 tcp dport 443 accept ip saddr 10.20.0.0/16 tcp dport 22 accept } }不要直接复制到远程服务器执行。应先确认管理地址、健康检查地址、IPv6 入口和应急回滚方式,否则容易把运维连接一并阻断。对于支持回源认证的 CDN,还可以叠加双向 TLS 或回源签名,避免只依赖可能变化的 IP 网段。权限边界修复管理接口不应只依赖来源 IP。比较稳妥的组合是:网络层限制源站和管理入口的可达范围。代理层只接受受信节点写入的客户端地址。应用层使用独立身份认证和角色授权,IP 白名单只作为附加条件。限速同时绑定账号、设备或会话维度,避免仅靠 IP。后台高风险操作记录认证主体、请求标识、来源链和操作结果。Nginx 侧可以保留网段限制,但不能替代应用认证:location /ops/ { allow 10.20.0.0/16; deny all; auth_request /internal-auth; proxy_pass } location = /internal-auth { internal; proxy_pass proxy_pass_request_body off; proxy_set_header Content-Length ''; proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Request-ID $request_id; }修复后的复测从正常 CDN 路径访问,业务功能和真实客户端地址记录正常。提交伪造 X-Forwarded-For、X-Real-IP 后,权限和限速结果不发生变化。使用源站地址直连时,连接在安全组或防火墙层被拒绝。从非可信代理发送专用客户端地址头时,Nginx 不采用该值。管理接口同时校验来源范围、登录状态和角色权限。Nginx、CDN 和应用日志能通过请求标识完成关联。复测时需要特别关注配置更新后的网段同步机制。CDN 节点网段发生变化后,如果仍依赖人工维护,可能出现回源中断,或者为了恢复业务临时放开全部公网地址,导致问题重新出现。总结CDN 和反向代理接入后,客户端 IP 已经成为一条需要明确验证的信任链。真正需要确认的不是“日志里能否看到真实 IP”,而是连接来自谁、请求头由谁写入、应用信任哪一跳,以及源站是否允许绕过入口直接访问。修复应同时覆盖源站网络边界、代理头处理、应用授权和日志关联,单独修改某个 Header 的读取方式通常不够。 网络请求、日志与边界流量分析示意 服务器、云资产与权限边界梳理示意
7小时前7小时 这类问题我一般先把“客户端真实 IP”和“源站访问入口”拆开排查,不要把 X-Forwarded-For 当成天然可信字段。真正的信任边界应该是:只有来自 CDN 官方回源网段的请求,才允许覆盖应用看到的客户端地址。 例如 Nginx 不建议直接写成: proxy_set_header X-Real-IP $http_x_forwarded_for; 否则用户可以自己构造请求头,日志、限流和审计都会被污染。可以使用 CDN 公布的固定回源网段,并启用 realip 模块: # 仅示例,实际网段必须替换为 CDN 官方公布的回源地址 set_real_ip_from 203.0.113.0/24; set_real_ip_from 2001:db8:1234::/48; real_ip_header X-Forwarded-For; real_ip_recursive on; log_format main '$remote_addr - $realip_remote_addr ' '"$request" "$http_x_forwarded_for"'; server { listen 443 ssl; server_name app.example.com; # 应用层使用 $remote_addr,只有可信 CDN 请求才会被 realip 重写 location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass } } 如果前面还有 WAF、SLB 或多级代理,最好明确每一跳的来源和头部覆盖规则,避免把任意客户端传来的 XFF 链直接透传到应用。可以先在源站抓一小段访问日志,对比以下字段: curl -sk \ -H 'Host: app.example.com' \ -H 'X-Forwarded-For: 198.51.100.10' sudo tcpdump -ni any 'tcp port 80 or tcp port 443' sudo ss -lntp 测试重点不是看页面能否打开,而是确认: 源站 80/443 是否只接受 CDN 回源网段,管理端口是否完全不暴露公网; 直接访问源站 IP 时,SNI、Host 不匹配是否会被默认虚拟主机拒绝; IPv4、IPv6 是否都做了相同的限制,实际排查中经常只封了 A 记录而漏掉 AAAA; DNS 历史、旧解析、邮件/监控/测试域名是否暴露过同一源站地址; 源站是否存在绕过 CDN 的上传、健康检查或回源专用域名。 源站侧最好再加一层网络访问控制,而不是只依赖 Host 校验。例如云安全组只放行 CDN 官方回源 CIDR;若必须保留直连管理,应使用独立管理入口、VPN 或零信任访问,不要和业务源站共用端口。 另外,如果 CDN 使用 PROXY protocol,就不要同时把它当作普通 HTTP 头解析。监听端和上游必须统一配置,否则可能出现连接解析异常,甚至把伪造的地址写入日志。最终验收建议用三组请求验证:正常 CDN 访问、直连源站 IP、伪造 XFF 的 CDN 访问,并分别检查应用日志、WAF 日志和源站 TCP 连接来源,确保三者的信任结果一致。
创建帐户或登录后发表意见