发布于3小时前3小时 背景网站接入 CDN、WAF、四层负载均衡或 Nginx 反向代理后,客户端地址通常通过 X-Forwarded-For、X-Real-IP 或厂商专用请求头向后传递。如果代理信任范围配置过宽,应用又把客户端 IP 用于后台鉴权、限流或内网接口判断,攻击者可能通过伪造请求头绕过权限边界。下面以已授权的网站安全测试为场景,核查三件事:真实 IP 是否只由可信代理提供、源站是否能被绕过代理直接访问、日志能否同时保留代理节点和最终客户端地址。测试前应明确目标域名、源站地址、测试窗口和允许验证的接口。本文示例只使用单次差异请求,不涉及扫描、口令尝试或破坏性操作。典型场景常见链路如下:客户端 -> CDN/WAF -> Nginx -> Web 应用 | +-> /admin、/internal、API应用可能存在类似判断:来源为 127.0.0.1、内网网段或办公出口 IP 时允许访问管理接口。如果应用直接读取未经校验的 X-Forwarded-For,而 Nginx 又原样转发用户提交的请求头,就会把一个网络属性错误地当成身份凭据。问题定位先从服务器侧确认真实配置,不要只看单个虚拟主机文件。Nginx 的 include、容器挂载和自动化模板经常导致运行配置与仓库配置不一致。# 导出 Nginx 当前生效配置并检查语法 sudo nginx -T > /tmp/nginx-effective.conf 2>&1 sudo nginx -t # 查找真实 IP、转发头和访问控制相关配置 grep -nE 'set_real_ip_from|real_ip_header|real_ip_recursive|proxy_set_header|allow |deny ' /tmp/nginx-effective.conf # 确认监听地址,重点检查源站是否直接监听公网 sudo ss -lntp | grep -E ':(80|443|8080|8443)[[:space:]]' # 查看服务启动参数和近期错误日志 systemctl status nginx --no-pager sudo tail -n 100 /var/log/nginx/error.log重点检查以下问题:是否存在 set_real_ip_from 0.0.0.0/0 或 ::/0。是否把整个内网网段设为可信代理,而该网段中还有普通业务主机。源站 80、443 端口是否允许任意公网地址连接。第一层反向代理是否覆盖外部传入的 X-Forwarded-For,而不是无条件追加。应用是否用客户端 IP 代替正式认证,或对 /admin、/internal 使用基于 IP 的单一放行。授权验证步骤先记录正常请求的状态码、响应长度和请求标识,再分别提交伪造头。不要只观察页面文字,302 跳转、缓存响应和统一错误页都可能造成误判。# 正常基线 curl -kisS --connect-timeout 5 \ -o /tmp/base.out -D /tmp/base.hdr # 验证应用是否错误信任 X-Forwarded-For curl -kisS --connect-timeout 5 \ -H 'X-Forwarded-For: 127.0.0.1' \ \ -o /tmp/xff.out -D /tmp/xff.hdr # 验证是否错误信任 X-Real-IP curl -kisS --connect-timeout 5 \ -H 'X-Real-IP: 127.0.0.1' \ \ -o /tmp/xri.out -D /tmp/xri.hdr wc -c /tmp/base.out /tmp/xff.out /tmp/xri.out grep -iE 'HTTP/|location:|x-request-id:|server:|via:' /tmp/*.hdr如果正常请求返回 401 或 403,而带伪造头的请求变为 200,需立即结合代理日志和应用日志确认请求实际经过的链路。只有响应体略有变化不能直接判定绕过,差异也可能来自缓存、动态令牌或风控页面。已知源站地址且授权范围允许时,可验证是否能绕过 CDN 直连。使用 --resolve 可以同时保持正确的 Host 和 TLS SNI:# 192.0.2.10 为文档示例地址,实际测试应使用授权资产 curl -kisS --resolve portal.example.test:443:192.0.2.10 \ \ -H 'X-Forwarded-For: 127.0.0.1'如果经 CDN 访问时伪造头无效,但直连源站时生效,通常说明边缘节点做了请求头清洗,而源站仍错误地把任意连接方视为可信代理。这类问题应同时修复源站网络暴露和真实 IP 信任配置。Nginx 真实 IP 处理细节在启用 realip 模块前,remote_addr 表示与 Nginx 建立 TCP 连接的地址,通常是 CDN 或负载均衡节点。启用后,Nginx 会从指定请求头重写 remote_addr;realip_remote_addr 则保留原始对端地址。可信范围必须精确到实际代理节点网段。下面以内部负载均衡网段为例:# 仅信任确实会连接到本机的代理地址 set_real_ip_from 10.20.10.0/24; set_real_ip_from 2001:db8:20:10::/64; real_ip_header X-Forwarded-For; real_ip_recursive on; location / { proxy_pass # realip 处理后,remote_addr 应为已验证的客户端地址 # 第一层自有代理建议覆盖外部传入值,不要盲目继承 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $host; proxy_set_header X-Request-ID $request_id; }是否使用 proxy_add_x_forwarded_for 取决于链路设计。它会把当前 remote_addr 追加到现有 X-Forwarded-For,因此在公网入口未先清洗请求头时,可能保留攻击者提交的内容。更稳妥的做法是由最外层可信入口覆盖该头,后续代理再按照固定规则追加,并让应用只信任明确的代理跳数或网段。如果 CDN 提供专用真实 IP 头,应使用厂商明确承诺会覆盖且无法由客户端透传的头,同时定期同步其出口网段。不要仅凭请求中出现某个厂商头就认为流量经过该厂商。应用侧权限边界即使真实 IP 解析正确,也不建议把来源 IP 作为管理接口的唯一认证条件。IP 可用于附加限制,但正式边界应由 VPN、双向 TLS、OIDC、短期令牌或独立管理网关承担。以 Express 为例,trust proxy 不应简单设置为 true,特别是在请求可能通过不同数量代理到达应用时。应明确可信代理网段:app.set('trust proxy', [ 'loopback', '10.20.10.0/24', '2001:db8:20:10::/64' ]);同时检查应用读取的是框架解析后的客户端地址,还是直接读取 req.headers 中的值。后者不具备可信链路语义。对于 /admin 和 /internal,建议先完成身份认证和权限校验,再把 IP 白名单作为额外条件;不要写成“命中内网地址即跳过认证”。日志留痕只记录 remote_addr 会在启用 realip 后丢失代理节点信息,只记录 X-Forwarded-For 又无法区分可信值和用户输入。建议同时记录原始连接方、解析后的客户端地址、原始转发头、请求 ID、上游地址和响应状态。log_format proxy_trace escape=json '{"time":"$time_iso8601",' '"request_id":"$request_id",' '"peer_ip":"$realip_remote_addr",' '"client_ip":"$remote_addr",' '"xff":"$http_x_forwarded_for",' '"host":"$host",' '"request":"$request",' '"status":$status,' '"bytes":$body_bytes_sent,' '"upstream":"$upstream_addr",' '"upstream_status":"$upstream_status",' '"duration":$request_time}'; access_log /var/log/nginx/proxy-trace.log proxy_trace;应用日志应继续传递同一个 X-Request-ID,便于从 CDN、Nginx、应用和数据库审计日志中串联一次请求。不要记录 Cookie、Authorization、会话令牌或完整敏感查询参数。源站网络收口源站不应只依赖 Host 校验隐藏自己。应在云安全组、防火墙或前置 ACL 上,只允许 CDN、WAF、负载均衡和管理出口访问业务端口。执行前要确认健康检查、证书续期、监控探针和应急管理链路,避免误封。# nftables 规则示意,地址需替换为实际可信代理网段 nft add table inet web_filter nft 'add chain inet web_filter input { type filter hook input priority 0; policy accept; }' nft add rule inet web_filter input tcp dport '{ 80, 443 }' ip saddr 203.0.113.0/24 accept nft add rule inet web_filter input tcp dport '{ 80, 443 }' ip6 saddr 2001:db8:30::/48 accept nft add rule inet web_filter input tcp dport '{ 80, 443 }' counter drop # 变更后确认规则与计数器 nft list ruleset生产环境应通过现有防火墙管理体系持久化规则,不要只执行临时命令。CDN 地址段变化时,需要有自动同步、变更审核和失败回滚机制。修复后的复测从正常入口提交伪造 X-Forwarded-For 和 X-Real-IP,确认权限结果不发生变化。从非可信网络直连源站,确认连接被网络层拒绝,而不是仅返回一个可识别的错误页。确认 peer_ip 为实际代理地址,client_ip 为预期客户端地址,原始转发头仍可用于审计。检查 IPv4、IPv6、HTTP/2、WebSocket 和健康检查链路,避免只修复单一路径。确认 401、403、302 和缓存命中情况下均不会泄露后台内容。验证应用不再以客户端 IP 单独授予管理员或内网权限。总结反向代理和加速器场景中的核心不是“拿到真实 IP”,而是建立可验证的信任链:只有指定代理可以声明客户端地址,源站只接受可信代理连接,应用不把网络位置当成唯一身份,日志同时保留原始对端和解析结果。排查时将请求差异、运行配置、网络暴露和日志证据放在一起判断,才能避免把缓存差异当成漏洞,也能防止修完请求头后仍留下可直连源站这一条绕过路径。 网络请求、日志与边界流量分析示意 服务器、云资产与权限边界梳理示意
2小时前2小时 这类场景最容易漏的不是“能否拿到真实 IP”,而是谁有资格声明真实 IP,以及绕过加速器后鉴权是否仍然成立。建议按下面三条链分别核查。 1. 真实 IP:只信任明确的代理链 不要直接使用客户端可控的 X-Forwarded-For。以 Nginx 为例,只把 CDN、负载均衡器的出口网段加入信任列表: set_real_ip_from 10.20.0.0/16; # 内部负载均衡网段 set_real_ip_from 203.0.113.0/24; # CDN 官方出口网段,示例地址 real_ip_header X-Forwarded-For; real_ip_recursive on; real_ip_recursive on 会从代理链右侧向左找第一个非可信地址。CDN 网段需要定期从官方接口同步,不能长期手工写死。 可以伪造请求头验证配置是否失效: curl -kI \ -H 'X-Forwarded-For: 1.1.1.1, 2.2.2.2' 检查日志中的客户端 IP 是否被直接改成 1.1.1.1。如果普通公网请求可以影响限流、封禁或审计 IP,说明信任边界配置过宽。 2. 源站绕过:鉴权不能只放在加速器侧 先确认源站 IP 是否能被直接访问: curl -kI --resolve \ curl -kI \ -H 'Host: 重点比较经 CDN 和直连源站时,管理接口、API、登录态校验、限流结果是否一致。源站最好在网络层只允许 CDN/LB 出口访问;条件允许时再加 CDN 到源站的 mTLS。 如果只能使用“回源密钥请求头”,必须由 CDN覆盖同名客户端请求头,而不是原样转发;源站对缺失或错误值直接拒绝。不过这只能算补充措施,优先级低于防火墙白名单和 mTLS。 应用接收的身份头也要注意。类似 X-User、X-Role 不能直接信任客户端输入,应先清除,再由可信鉴权模块写入: location /api/ { auth_request /_auth; auth_request_set $auth_user $upstream_http_x_authenticated_user; proxy_set_header X-User ""; proxy_set_header X-Role ""; proxy_set_header X-Authenticated-User $auth_user; proxy_pass } 同时建议列一个接口矩阵,逐项验证“边缘鉴权关闭、直连源站、伪造身份头、过期令牌”四种情况。真正敏感的权限判断仍应落在应用或统一鉴权服务,不能只依赖 CDN 规则。 3. 缓存边界:避免把用户态响应缓存成公共内容 带 Authorization、会话 Cookie 或 Set-Cookie 的响应,应默认绕过共享缓存: proxy_cache_bypass $http_authorization $cookie_session; proxy_no_cache $http_authorization $cookie_session $upstream_http_set_cookie; 账户页、订单页等敏感接口建议由应用明确返回: Cache-Control: private, no-store 测试时可分别用两个账号请求同一 URL,观察 Age、X-Cache 等响应头,确认账号 B 不会命中账号 A 的响应。还要检查缓存键是否遗漏租户 ID、语言、设备类型等实际影响响应内容的维度。 4. 日志同时保留“连接来源”和“解释后的客户端 IP” 只记录 $remote_addr 不够。启用 realip 模块后,它可能已经被改写;建议同时保留原始连接地址、完整转发链和请求 ID: log_format audit escape=json '{"time":"$time_iso8601",' '"request_id":"$request_id",' '"client_ip":"$remote_addr",' '"peer_ip":"$realip_remote_addr",' '"xff":"$http_x_forwarded_for",' '"host":"$host",' '"method":"$request_method",' '"uri":"$uri",' '"status":$status,' '"upstream":"$upstream_addr",' '"upstream_status":"$upstream_status",' '"request_time":$request_time}'; access_log /var/log/nginx/access-audit.json audit; proxy_set_header X-Request-ID $request_id; add_header X-Request-ID $request_id always; 不要记录 Authorization、完整 Cookie、密码、验证码和敏感查询参数。日志侧至少应能关联 CDN 请求 ID、源站请求 ID、应用审计事件,并对“直连源站”“异常长 XFF 链”“同一身份跨大量 IP”“鉴权失败突增”设置告警。 最后可用一个简单标准判断是否核查到位:客户端不能伪造审计 IP,源站不能绕过边缘防护,身份信息不能由客户端声明,用户态响应不能进入公共缓存,且一次请求能从边缘一路追踪到应用。
创建帐户或登录后发表意见