发布于8小时前8小时 背景网站接入 CDN、WAF 或四层负载均衡后,源站看到的连接地址通常是代理节点,而不是访问者地址。很多站点会直接读取 X-Forwarded-For,并将它用于后台 IP 白名单、限速、审计甚至登录风控。只要代理信任链没有定义清楚,就可能出现伪造客户端 IP、绕过访问控制、日志无法溯源等问题。本文以“客户端 → CDN/WAF → Nginx 源站 → 应用服务”为例,说明如何在授权测试中验证这条链路,并给出源站隔离、Nginx realip 配置和日志修复方法。测试前应确认域名、源站地址和测试时间均在授权范围内。针对源站直连的验证应控制请求频率,避免绕过 CDN 后给后端造成额外压力。问题定位这类问题通常集中在三个位置:源站 80、443 端口直接暴露在公网,任何地址都能绕过 CDN 连接。Nginx 无条件信任 X-Forwarded-For、X-Real-IP 等客户端可控请求头。应用自行拆分 X-Forwarded-For,并直接取最左侧地址作为真实客户端 IP。先在源站确认实际监听情况、realip 模块和当前生效配置。不要只检查某个配置文件,nginx -T 才能看到 include 后的完整结果。nginx -V 2>&1 | grep -o http_realip_module nginx -T 2>&1 | grep -nE 'set_real_ip_from|real_ip_header|real_ip_recursive|proxy_set_header|log_format' ss -lntp | grep -E ':(80|443)[[:space:]]' nft list ruleset 如果 Nginx 编译参数中没有 realip 模块,需要安装发行版提供的完整构建包或重新构建,不能用应用层随意读取请求头代替。技术分析:代理信任链如何失效X-Forwarded-For 本质上只是一个 HTTP 请求头。客户端可以主动发送如下内容:X-Forwarded-For: 127.0.0.1 X-Real-IP: 10.0.0.8 安全性取决于最靠近源站、且受控制的代理如何处理这些字段。常见情况有三种:边缘代理覆盖请求头:源站收到的值由 CDN 重新生成,可信度最高。边缘代理在原值右侧追加真实地址:源站必须从右向左解析,并仅跳过明确受信任的代理地址。边缘代理原样透传:该字段不可用于鉴权或审计,应改用厂商提供且会被覆盖的专用客户端地址头。Nginx 的 set_real_ip_from 用于声明哪些连接来源有资格提供真实地址,real_ip_recursive on 会从地址链右侧开始跳过可信代理,选出最后一个非可信地址。这里最危险的配置是 set_real_ip_from 0.0.0.0/0,它等价于允许任意公网客户端声明自己的来源地址。授权验证步骤第一步通过正常域名访问,在请求中加入一个测试地址,然后检查源站日志。预期结果是最终识别出的客户端地址仍为测试机的公网出口地址,而不是主动注入的 127.0.0.1。curl -sk -o /dev/null -D - \ -H 'X-Forwarded-For: 127.0.0.1' \ -H 'X-Real-IP: 10.0.0.8' \ ' 第二步验证源站是否可以绕过 CDN 直连。下面的 ORIGIN_IP 仅替换为授权范围内的源站地址:curl -sk --connect-timeout 5 \ --resolve app.example.com:443:ORIGIN_IP \ -H 'X-Forwarded-For: 127.0.0.1' \ ' 修复后的预期结果应是连接超时、被防火墙拒绝,或者由源站额外认证机制返回拒绝响应。如果直连请求正常进入应用,就不能只依赖 Nginx 的请求头解析,还需要先收紧网络边界。第三步检查依赖 IP 白名单的管理入口。若 /ops、/admin 等路径只通过 allow、deny 或应用内 IP 判断进行保护,应分别测试正常 CDN 路径、伪造请求头和源站直连。IP 白名单只能作为附加控制,不能代替账号认证、多因素认证或管理 VPN。Nginx 可信代理配置以下网段使用的是文档示例地址,部署时必须替换为 CDN 或负载均衡厂商发布的当前出口网段,并同时处理 IPv4、IPv6。不要从访问日志中临时收集地址后直接加入信任列表。# /etc/nginx/conf.d/realip.conf # 示例地址,禁止原样用于生产 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; 如果 CDN 提供专用客户端地址头,并明确保证会在边缘覆盖该字段,优先使用专用头:set_real_ip_from 203.0.113.0/24; set_real_ip_from 2001:db8:1234::/48; real_ip_header CDN-Connecting-IP; 向应用转发时,建议在 Nginx 这一信任边界重新生成请求头,不要把外部传入的整条链原样交给应用。若后端只需要最终客户端地址,可以直接覆盖:location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Request-ID $request_id; proxy_pass } 应用侧应读取反向代理传入的固定字段,并确保应用端口只允许 Nginx 或内部负载均衡访问。如果应用端口也暴露给普通客户端,请求头依然可以被绕过。限制源站只能由加速节点访问网络层限制优先于请求头判断。下面是 nftables 的结构示例,执行前应确认有控制台或带外管理通道,避免错误策略导致服务器失联:table inet edge_filter { set cdn4 { 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 10.20.0.0/16 tcp dport 22 accept # 仅允许 CDN 到达 Web 端口 ip saddr @cdn4 tcp dport { 80, 443 } accept } } IPv6 不能遗漏。如果域名存在 AAAA 记录,而防火墙只限制 IPv4,攻击者仍可能通过 IPv6 直连源站。云服务器还应同步检查安全组、负载均衡监听器和容器端口映射。日志留痕与排查排查时至少要同时记录 TCP 对端地址、realip 解析后的客户端地址和原始转发头。Nginx 中 $realip_remote_addr 表示被替换前的连接对端,通常应是 CDN 节点;$remote_addr 是解析后的客户端地址。log_format edge_json escape=json '{"time":"$time_iso8601",' '"request_id":"$request_id",' '"host":"$host",' '"peer":"$realip_remote_addr",' '"client":"$remote_addr",' '"xff":"$http_x_forwarded_for",' '"method":"$request_method",' '"uri":"$uri",' '"status":$status,' '"upstream":"$upstream_addr",' '"upstream_status":"$upstream_status"}'; access_log /var/log/nginx/edge_access.json edge_json; 配置修改后先执行语法检查,再平滑加载:nginx -t && nginx -s reload tail -F /var/log/nginx/edge_access.json | \ jq -c 'select(.host == "app.example.com") | {peer,client,xff,status,request_id}' 正常情况下,peer 应落在可信 CDN 网段内,client 应与测试出口一致。若 client 等于主动注入的 127.0.0.1,说明边缘没有覆盖请求头,或源站错误信任了连接来源。日志中不要记录 Authorization、Cookie、会话令牌和完整查询参数,避免修复溯源问题时引入新的凭据泄露风险。权限边界与修复优先级第一优先级:在安全组或主机防火墙限制源站,仅允许 CDN、WAF 或内部负载均衡访问。第二优先级:准确维护 set_real_ip_from,禁止使用 0.0.0.0/0、::/0 或过宽的内部网段。第三优先级:确认边缘节点会覆盖或安全追加客户端地址头;无法确认时使用厂商专用头。第四优先级:应用不自行解析不受控的 X-Forwarded-For,后端端口只对反向代理开放。第五优先级:后台入口使用独立认证、MFA、管理 VPN 或 mTLS,IP 白名单只作为附加条件。第六优先级:保留 peer、client、xff 和 request_id,并将日志集中存储,防止源站被入侵后本地日志一并删除。总结网站加速链路中的真实 IP 不是简单读取一个请求头,而是代理身份、网络入口和解析规则共同构成的信任链。有效修复需要同时完成三件事:源站不可被普通客户端直连、Nginx 只信任明确的代理网段、应用只接收由受控反向代理重新生成的地址字段。验证完成后,再通过结构化日志确认连接对端与解析结果一致,才能兼顾权限边界、风控准确性和事件溯源。 网络请求、日志与边界流量分析示意 服务器、云资产与权限边界梳理示意
7小时前7小时 这类链路最容易出问题的点,不是“有没有读取 X-Forwarded-For”,而是谁有权限写入,以及哪一层负责清洗。建议把加速节点设为唯一信任边界:源站只接受加速节点回源,并且只信任这些节点写入的客户端 IP 头。否则攻击者直连源站时,可以自行构造请求头绕过 IP 限制或污染审计日志。 Nginx 源站可以按下面的思路配置,信任网段应从加速服务商的官方接口定期同步,不要直接信任全部地址: http { # 仅填写实际反向代理/CDN 的回源网段 set_real_ip_from 192.0.2.0/24; set_real_ip_from 2001:db8:100::/48; # 按服务商实际使用的头调整,也可能是 CF-Connecting-IP 等 real_ip_header X-Forwarded-For; real_ip_recursive on; log_format upstream_audit '$time_iso8601 ' 'peer=$realip_remote_addr ' 'client=$remote_addr ' 'xff="$http_x_forwarded_for" ' 'host="$host" request="$request" ' 'status=$status bytes=$body_bytes_sent ' 'request_id="$request_id"'; access_log /var/log/nginx/access.log upstream_audit; } 这里建议同时记录 $realip_remote_addr 和 $remote_addr:前者保留实际连接到源站的代理地址,后者是经过 realip 模块解析后的客户端地址。出现误封、伪造或多层代理解析异常时,两者可以互相校验。不要只覆盖成一个“真实 IP”,否则后续很难还原链路。 权限边界还应落实到网络层。例如只允许 CDN 网段访问源站 80/443,管理入口单独走 VPN 或堡垒机: # 示例,执行前按实际网段和现有防火墙规则调整 iptables -A INPUT -p tcp -s 192.0.2.0/24 --dport 443 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j DROP 应用层也不要再次自行取 X-Forwarded-For 的第一个值。应用应优先使用 Web 服务器已经校验并重写后的远端地址;如果框架必须配置代理层数或可信代理列表,应显式填写网段,避免使用“信任任意代理”的配置。 修复后可以做三组验证: # 1. 直连源站并伪造头,确认被网络层拒绝 curl -vk --resolve example.com:443:ORIGIN_IP \ -H 'X-Forwarded-For: 1.1.1.1' # 2. 经正常域名访问并携带伪造头,确认加速节点会覆盖或规范化 curl -vk -H 'X-Forwarded-For: 1.1.1.1' # 3. 检查日志中的代理地址、解析后地址和原始头是否符合预期 tail -f /var/log/nginx/access.log 如果链路是“客户端 → CDN → WAF/负载均衡 → Nginx”,还要逐跳确认每一层到底是覆盖、追加还是透传请求头。比较稳妥的原则是:边界节点先删除外部传入的同名头,再写入可信值;后续节点只接受来自上一跳可信网段的数据。限流、后台白名单和风控规则也应统一使用经过该信任链解析后的地址,避免日志修好了,鉴权逻辑仍在读取可伪造头。
创建帐户或登录后发表意见