发布于21小时前21小时 背景网站接入 CDN、WAF 或反向代理后,常见架构是“用户 → CDN/WAF → Nginx → 应用”。这类改造看似只是加速和防护,实际上会改变客户端 IP 的信任边界。授权测试中经常遇到两类问题:一是源站仍可被公网直接访问,从而绕过 CDN/WAF;二是应用无条件信任 X-Forwarded-For,导致后台 IP 白名单、限流和审计日志被伪造。下面以自有或明确授权的网站为例,说明如何定位问题。示例中的 192.0.2.0/24、198.51.100.0/24 和 203.0.113.0/24 均为文档保留地址,实际操作时应替换为测试环境地址。典型场景某站点将管理接口限制为仅允许 127.0.0.1 或办公网访问,应用直接从 X-Forwarded-For 读取客户端地址。公网入口虽然接入了 CDN,但源站 443 端口仍对全网开放。攻击链路通常不是 CDN 本身被绕过,而是同时存在以下配置缺陷:源站公网 IP 可通过历史 DNS、证书透明度记录、邮件头或资产台账泄露。源站防火墙没有限制 CDN 回源地址段。Nginx 使用 proxy_add_x_forwarded_for 继续传递外部可控的请求头。应用将 X-Forwarded-For 最左侧地址直接作为真实客户端 IP。管理接口、限流或风控策略依赖这个未经验证的地址。问题定位先确认实际链路,不要只看域名解析结果。需要同时检查 DNS、监听端口、Nginx 生效配置、主机防火墙和应用框架的代理信任设置。# 查看域名当前解析和响应头 dig +short site.example curl -skI # 在源站检查监听地址及 Nginx 完整生效配置 ss -lntp nginx -T 2>&1 | grep -E 'listen|server_name|real_ip|proxy_set_header|allow|deny' # 检查主机防火墙;按实际使用的防火墙选择执行 nft list ruleset iptables-save # 检索应用是否直接信任代理头 grep -RniE 'X-Forwarded-For|X-Real-IP|trust proxy|ProxyFix|REMOTE_ADDR' /opt/app /var/www 2>/dev/null如果已经获得授权源站 IP,可以使用 --resolve 保持正确的 Host 和 TLS SNI,同时直接连接源站。不要用裸 IP 请求代替,因为虚拟主机和证书配置可能导致结论失真。# 正常通过域名访问 curl -sk -D - -o /dev/null # 绕过公共 DNS,直接连接授权范围内的源站 curl -sk --resolve site.example:443:203.0.113.10 \ -D - -o /dev/null # 验证源站是否错误信任外部传入的客户端 IP 头 curl -sk --resolve site.example:443:203.0.113.10 \ -H 'X-Forwarded-For: 127.0.0.1' \ -H 'X-Real-IP: 127.0.0.1' \ -D - -o /dev/null重点比较状态码、响应长度、跳转位置和响应时间。如果普通请求返回 403,而伪造代理头后变成 200,就应继续确认授权逻辑到底读取了哪个字段。验证到边界变化即可,不需要访问或修改业务数据。技术分析:X-Forwarded-For 为什么容易配错X-Forwarded-For 只是普通 HTTP 请求头,客户端可以自行构造。例如客户端发送 X-Forwarded-For: 127.0.0.1,代理再使用 proxy_add_x_forwarded_for 时,传给应用的值可能变成“127.0.0.1, 客户端公网地址”。如果应用简单取第一个地址,伪造值就会被当作真实 IP。Nginx 的 realip 模块只有在请求来源属于 set_real_ip_from 声明的可信代理时,才应采用代理头中的地址。real_ip_recursive on 用于多级可信代理链,但前提是可信地址段准确;将 0.0.0.0/0 配置为可信代理等同于允许任何人改写客户端地址。变量含义建议用途$remote_addr启用 realip 后表示解析出的客户端地址传给后端作为规范化客户端 IP$realip_remote_addr改写前与 Nginx 建立连接的地址记录实际 CDN、负载均衡或直连来源$http_x_forwarded_for请求中原始 X-Forwarded-For 内容仅用于审计,不应直接用于授权Nginx 修复配置示例以下示例假设 CDN 官方回源网段为 192.0.2.0/24 和 198.51.100.0/24。生产环境应从服务商官方接口或文档同步网段,不能照抄示例地址。http { # 只信任明确的 CDN/WAF 回源地址段 set_real_ip_from 192.0.2.0/24; set_real_ip_from 198.51.100.0/24; # 按服务商文档选择 X-Forwarded-For、CF-Connecting-IP 等字段 real_ip_header X-Forwarded-For; real_ip_recursive on; log_format edge escape=json '{"time":"$time_iso8601",' '"request_id":"$request_id",' '"peer":"$realip_remote_addr",' '"client":"$remote_addr",' '"xff":"$http_x_forwarded_for",' '"host":"$host",' '"request":"$request",' '"status":$status,' '"upstream":"$upstream_addr",' '"request_time":$request_time,' '"upstream_time":"$upstream_response_time"}'; access_log /var/log/nginx/edge_access.log edge; server { listen 443 ssl; server_name site.example; location / { proxy_pass # 覆盖外部传入值,不把未验证的链条继续交给应用 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; proxy_set_header X-Request-ID $request_id; } } }如果架构中存在多层自建代理,应逐层明确谁负责清洗请求头、谁负责追加地址。不要让每一层都无条件使用 proxy_add_x_forwarded_for。最外层可信入口负责丢弃客户端传入值并生成规范化头,内层仅信任固定上游。限制源站入口仅修正请求头还不够。只要源站能够被公网直连,攻击者仍可能绕过 CDN 的 WAF、速率限制、Bot 管理和证书策略。优先在云安全组、网络 ACL 或主机防火墙限制 80/443 只允许 CDN 回源网段,并为运维保留独立的 VPN 或堡垒机链路。# nftables 示例:执行前先确认 SSH/VPN 管理链路,避免锁死服务器 nft add table inet edge_filter nft 'add chain inet edge_filter input { type filter hook input priority 0; policy accept; }' # 允许已建立连接和本机流量 nft add rule inet edge_filter input ct state established,related accept nft add rule inet edge_filter input iif lo accept # 允许示例 CDN 网段访问 HTTPS nft add rule inet edge_filter input ip saddr 192.0.2.0/24 tcp dport 443 accept nft add rule inet edge_filter input ip saddr 198.51.100.0/24 tcp dport 443 accept # 允许管理 VPN 网段访问 SSH nft add rule inet edge_filter input ip saddr 10.20.0.0/16 tcp dport 22 accept # 拒绝其他来源直接访问 Web 和 SSH nft add rule inet edge_filter input tcp dport { 80, 443, 22 } drop # 检查规则,不要在未持久化和未准备回滚时直接结束会话 nft list ruleset生产环境调整防火墙时应保留现有管理会话,先开第二个会话验证,再进行规则持久化。CDN 网段可能变化,应设置变更监控和定期同步,否则会出现回源中断。应用层权限边界客户端 IP 不适合作为唯一身份凭据。后台接口即使有 IP 白名单,也应叠加登录认证、短期会话、MFA 或双向 TLS。应用应读取 Web 服务器传入的规范化字段,并保证应用端口只监听回环地址或内网地址。以 Express 为例,如果 Nginx 与应用位于同一主机,可只信任回环代理,不要直接设置为 true:app.set('trust proxy', ['loopback']); // req.ip 只有在请求确实经过受信代理时才有意义 app.get('/admin/status', requireLogin, requireAdmin, (req, res) => { auditLog.info({ requestId: req.get('X-Request-ID'), clientIp: req.ip, userId: req.user.id }); res.json({ status: 'ok' }); });如果应用容器通过 Docker 网桥或 Kubernetes Ingress 访问,应填写准确的代理网段,并确认该网段不会被普通业务容器占用。类似 ProxyFix、ForwardedHeadersMiddleware 等中间件只能控制解析层数,不能代替网络来源限制。日志留痕与复核排查这类问题时,至少同时保留“实际连接来源”和“解析后的客户端地址”。只记录其中一个,会导致后续无法区分 CDN 回源、源站直连和请求头伪造。peer:与 Nginx 建立 TCP 连接的地址,用于判断是否来自 CDN。client:经过可信代理规则解析后的客户端地址。xff:原始代理头,仅用于取证和发现异常,不作为授权依据。request_id:贯穿 CDN、Nginx 和应用日志,便于关联。upstream、request_time、upstream_time:用于区分源站性能问题和边缘节点问题。# 查找绕过 CDN 直接访问源站的记录 jq -c 'select(.peer != "192.0.2.10" and .peer != "198.51.100.10")' \ /var/log/nginx/edge_access.log # 查找声称来自本机或内网的异常代理头 grep -E '"xff":"(127\.0\.0\.1|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)' \ /var/log/nginx/edge_access.log # 配置修改后的语法检查和平滑加载 nginx -t && nginx -s reload修复后的验证清单通过正常域名访问业务,确认 CDN 回源和静态资源正常。使用 --resolve 直连源站,预期应超时、连接拒绝或返回统一拒绝页面。通过正常入口发送伪造 X-Forwarded-For,确认应用看到的是实际测试客户端地址。检查限流、后台白名单和审计日志是否基于规范化后的地址。确认 Nginx 日志同时记录 peer、client、xff 和 request_id。检查源站是否还有 IPv6、备用端口、旧负载均衡或测试域名可绕过。复核 CDN 回源网段更新机制,并准备错误更新时的回滚方案。总结网站接入 CDN 或加速器后,安全边界不再只是 Nginx 配置,而是 DNS、源站网络访问控制、代理头清洗、应用代理信任和日志留痕共同组成的链路。排查时应先确认源站能否被直连,再验证请求头是否可控,最后检查应用是否将客户端 IP 用于授权。可靠的修复原则是:源站只接受可信回源、入口统一覆盖代理头、应用只信任固定代理、重要权限不单独依赖 IP,并在日志中同时保留连接来源与解析结果。 网络请求、日志与边界流量分析示意 服务器、云资产与权限边界梳理示意
20小时前20小时 这类排查建议拆成三条链路看:源站是否可直连、真实 IP 是否可伪造、边缘与源站日志能否对齐。只检查 DNS 当前解析通常不够。 确认源站直连是否能绕过 CDN 拿到疑似源站 IP 后,不要只用 curl -H 'Host: ...',HTTPS 下还涉及 SNI,建议这样测: curl -vk --resolve \ openssl s_client -connect 203.0.113.10:443 \ -servername 如果返回与正式站点一致的证书和业务内容,说明源站可直接访问,CDN/WAF 可以被绕过。源站 IP 常从历史 DNS、邮件头、旁站、旧子域、证书记录或错误页中泄露,修复后最好同步更换源站地址,否则旧记录仍有价值。 源站访问控制不要只依赖 Host 最稳妥的是安全组或防火墙只放行 CDN 官方出口网段;管理入口、监控和 ACME 验证单独规划。CDN 网段应通过官方接口定时更新,不要手工维护后长期不管。 # nftables 示例,网段仅为占位 table inet filter { set cdn_v4 { type ipv4_addr flags interval elements = { 192.0.2.0/24, 198.51.100.0/24 } } chain input { type filter hook input priority 0; ct state established,related accept iif "lo" accept tcp dport { 80, 443 } ip saddr @cdn_v4 accept tcp dport { 80, 443 } drop } } 如果 CDN 支持回源 mTLS/Authenticated Origin Pulls,建议同时开启。防火墙解决“谁能连”,mTLS 解决“连过来的是不是真正的 CDN”,两者不是替代关系。 真实 IP 只信任明确的上游代理 常见问题是应用直接读取 X-Forwarded-For,攻击者直连源站时自行构造该头,导致限速、审计和封禁全部失效。Nginx 应只对 CDN 网段启用 realip: set_real_ip_from 192.0.2.0/24; set_real_ip_from 198.51.100.0/24; # 按 CDN 实际文档选择,例如 X-Forwarded-For、 # CF-Connecting-IP、True-Client-IP 等 real_ip_header X-Forwarded-For; real_ip_recursive on; 不要配置成 set_real_ip_from 0.0.0.0/0。如果 CDN 后面还有 SLB/Nginx,需要把完整代理链画出来,并确认每一层是“覆盖”还是“追加”转发头;最外层代理应清除客户端自带的同名头。 日志同时记录客户端地址和实际对端 Nginx realip 生效后,$remote_addr 通常是还原后的客户端地址,$realip_remote_addr 是建立 TCP 连接的上游代理地址。建议两个都留: log_format trace escape=json '{"time":"$time_iso8601",' '"client_ip":"$remote_addr",' '"peer_ip":"$realip_remote_addr",' '"xff":"$http_x_forwarded_for",' '"request_id":"$request_id",' '"host":"$host",' '"request":"$request",' '"status":$status,' '"upstream_status":"$upstream_status",' '"request_time":$request_time,' '"upstream_time":"$upstream_response_time"}'; access_log /var/log/nginx/access_trace.log trace; proxy_set_header X-Request-ID $request_id; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 排查时可以同时观察连接来源,确认源站是否仍收到非 CDN 地址: ss -nt state established '( sport = :443 )' sudo tcpdump -ni any 'tcp dst port 443' 日志边界上需要注意:被 CDN/WAF 拦截的请求不会出现在源站访问日志里;源站超时、重置连接等问题又可能只在源站或负载均衡日志中体现。因此最好让 CDN 的请求 ID 原样回传并写入源站日志,用同一个 ID 串联边缘、反代和应用。日志里不要完整记录 Cookie、Authorization、URL 中的令牌等敏感字段。 最后可以做一个简单的闭环验证:公网直连源站应超时或拒绝;经 CDN 访问正常;伪造 X-Forwarded-For 后,源站记录的客户端 IP 不应被任意控制;源站看到的 TCP 对端必须落在 CDN 官方网段内。满足这几项,源站暴露和真实 IP 信任链才算基本收口。
创建帐户或登录后发表意见