跳转到帖子

咖啡馆实战:网站接入 CDN 后的源站暴露、真实 IP 信任链与日志留痕排查

精选回复

发布于

背景

网站接入 CDN 或高防后,常见误区是认为 DNS 已指向代理节点,源站就不会再被直接访问。授权测试中更值得检查的是三件事:源站端口是否仍对公网开放、反向代理是否错误信任客户端提交的 IP 请求头、绕过 CDN 后是否还能直接命中业务虚拟主机。

本文以 Nginx 源站为例,给出一套适合网站建设和服务器运维场景的验证流程。测试应限定在自有资产或书面授权范围内,避免对共享 CDN 节点和第三方地址进行扫描。

典型场景

某站点的公开域名指向 CDN,但源站 Nginx 仍监听公网地址的 80 和 443 端口。运维侧还配置了不受限制的 real_ip_header,导致任意直连请求都能通过 X-Forwarded-For 伪造访问来源。业务的登录审计、限速和后台 IP 白名单都依赖这个错误的客户端地址。

检查对象常见问题直接影响
源站网络边界80/443 对全网开放可绕过 CDN 的 WAF、限速和访问控制
Nginx 信任链set_real_ip_from 配置过宽客户端 IP 可伪造
虚拟主机默认站点直接承载业务使用源站 IP 即可访问
访问日志仅记录重写后的 remote_addr无法还原代理链和直连来源

问题定位

先从权威 DNS、历史解析记录、证书透明度日志和自有资产台账核对源站地址。历史 DNS 与证书信息只能作为线索,最终必须由资产负责人确认归属,不能据此测试不明 IP。

确认授权地址后,分别比较 CDN 路径与源站直连路径。直连 HTTPS 时使用 curl 的 resolve 参数,让 TLS 的 SNI 和 HTTP Host 保持为业务域名,而连接目标改为已确认的源站 IP。这样能判断源站是否仍接受公网请求。

# 公网正常访问路径
curl -skI 

# 仅对已确认属于本方的源站执行
curl -skI --resolve  \
  

# 检查证书、SNI 和握手结果
openssl s_client -connect 203.0.113.10:443 \
  -servername  -brief </dev/null

# 在源站本机确认监听地址及进程
sudo ss -lntp | grep -E ':(80|443)[[:space:]]'

# 展开 Nginx 完整配置,重点查看默认站点和真实 IP 模块
sudo nginx -T 2>&1 | grep -nE \
  'listen|server_name|default_server|real_ip_header|set_real_ip_from|real_ip_recursive|access_log'

技术分析:为什么伪造头会生效

X-Forwarded-For、X-Real-IP 等字段本质上都是普通 HTTP 请求头。只有请求确实来自受信任反向代理时,源站才能使用这些字段覆盖客户端地址。危险配置通常表现为信任全部来源,或者把整个内网和过大的云网段都列为可信代理。

# 危险示例:任何来源都可影响 $remote_addr
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

如果应用使用重写后的 remote_addr 做后台白名单,攻击者在可直连源站时可能提交伪造头绕过限制。验证时不要尝试猜测管理地址,可以在测试环境增加一个仅返回观测信息的临时 location,或直接比对 Nginx 日志字段。

可审计的验证步骤

  • 记录测试窗口、授权域名、源站 IP、测试人员出口 IP和工单编号。
  • 分别请求 CDN 地址与源站地址,保存响应状态、Server、Via、缓存头及页面摘要。
  • 使用一个明显的文档保留地址作为测试头值,例如 198.51.100.77,避免冒用真实第三方地址。
  • 同时观察访问日志中的 socket 对端地址、重写后地址和原始转发头。
  • 验证完成后删除临时 location,并保留配置变更记录。
# 授权环境中的最小验证请求
curl -sk --resolve  \
  -H 'X-Forwarded-For: 198.51.100.77' \
  -H 'X-Real-IP: 198.51.100.77' \
  -o /dev/null -w 'status=%{http_code} peer=%{remote_ip}\n' \
  

# 按测试出口、测试值和时间窗口检索日志
sudo grep -E '198\.51\.100\.77|测试出口IP' \
  /var/log/nginx/access_security.log

注意 curl 输出中的 remote_ip 是本次 TCP 连接目标,不代表服务端最终认定的客户端地址。真实判断应以源站日志或应用侧审计记录为准。

日志留痕配置

排查期间不建议只记录 remote_addr,因为启用 realip 模块后它可能已经被改写。Nginx 的 realip_remote_addr 可保留建立 TCP 连接的原始对端地址。日志中还应记录原始 Host、请求 ID 和代理头,便于区分 CDN 回源与公网直连。

log_format security escape=json
  '{"time":"$time_iso8601",'
  '"request_id":"$request_id",'
  '"socket_peer":"$realip_remote_addr",'
  '"client_ip":"$remote_addr",'
  '"xff":"$http_x_forwarded_for",'
  '"xri":"$http_x_real_ip",'
  '"host":"$host",'
  '"request":"$request",'
  '"status":$status,'
  '"bytes":$body_bytes_sent,'
  '"ua":"$http_user_agent"}';

access_log /var/log/nginx/access_security.log security;

如果 CDN 使用自定义请求 ID 或回源鉴权头,可以记录其存在性,但不要把长期密钥、Cookie、Authorization 或完整查询参数直接写入日志。日志文件应设置最小读取权限,并配置轮转和留存周期。

修复一:收紧源站网络边界

优先在云安全组、主机防火墙或上游 ACL 中只允许 CDN 官方回源网段访问 80/443,管理入口则只允许堡垒机或 VPN 网段。CDN 地址段会变化,应从供应商的官方接口同步并经过变更审核,不能长期复制一份静态列表。

# nftables 示例:先在维护窗口验证,避免把自己锁在服务器外
sudo nft add table inet webfilter
sudo nft 'add chain inet webfilter input { type filter hook input priority 0; policy accept; }'

# 示例网段必须替换为 CDN 官方当前公布的回源网段
sudo nft add rule inet webfilter input ip saddr 192.0.2.0/24 tcp dport {80,443} accept
sudo nft add rule inet webfilter input ip saddr 198.51.100.10 tcp dport 22 accept
sudo nft add rule inet webfilter input tcp dport {80,443} drop

sudo nft list ruleset
修改远程服务器防火墙前应保留现有 SSH 会话,并准备云控制台或带外管理。示例中的地址均为文档保留地址,不能直接用于生产。

修复二:建立明确的真实 IP 信任链

只信任实际会与源站建立连接的 CDN 节点网段,并使用供应商明确约定的真实 IP 头。若前面还有负载均衡器,需要逐跳确认谁负责覆盖、追加和清洗请求头。不能同时无条件信任多个来源不明的 IP 头。

# 示例:仅信任明确的代理网段
set_real_ip_from 192.0.2.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# 检查并平滑加载
sudo nginx -t && sudo systemctl reload nginx

对于能控制回源配置的环境,可叠加随机回源 Host、双向 TLS 或由 CDN 注入的高熵鉴权头。鉴权头必须在 CDN 边缘覆盖客户端同名字段,并定期轮换;它只能作为附加控制,不能替代网络层白名单。

修复三:拒绝未知 Host 和直接 IP 访问

单独配置默认虚拟主机,对未知 Host 直接关闭连接或返回固定错误页。业务虚拟主机只声明实际域名。该措施无法替代防火墙,因为攻击者仍可在直连源站时携带正确 Host,但它能减少误配置和资产暴露。

server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;

    # 默认站点使用独立证书配置,此处省略证书路径
    return 444;
}

server {
    listen 443 ssl;
    server_name 

    # 正常业务配置
}

复测与风险判断

  • 从非 CDN 出口访问源站 80/443,应超时或被明确拒绝。
  • 通过 CDN 访问业务,应保持正常,日志中的 socket_peer 应落在可信代理网段。
  • 提交伪造 X-Forwarded-For 后,非可信直连请求不能改变服务端认定的客户端地址。
  • 未知 Host 或直接使用 IP 的请求,不应落入业务虚拟主机。
  • 登录限速、后台白名单和风控策略应读取同一套经过验证的客户端地址。

如果源站可直连但业务没有任何边缘防护差异,风险通常为配置缺陷;如果 CDN 承担 WAF、鉴权、限速或地域限制,直连可绕过这些控制,应提高风险等级。若伪造真实 IP 还能绕过后台白名单、审计或账户限速,则应单独评估为权限边界失效。

总结

CDN 接入是否安全,关键不在于页面能否被加速,而在于源站是否只接受可信回源、代理头是否有严格信任边界、日志能否还原真实连接链路。比较稳妥的顺序是先确认资产和监听面,再验证直连与 Host 行为,随后检查 realip 配置和日志,最后从网络 ACL、代理信任链、默认虚拟主机三个层面修复并复测。

服务器与云环境安全示意图
服务器、云资产与权限边界梳理示意
网络流量与边界分析示意图
网络请求、日志与边界流量分析示意

这类问题建议拆成三条线同时查:源站是否还能被直连、真实 IP 是否只信任 CDN、日志能否还原请求经过的链路。只改应用层获取 IP 不够,源站入口必须先收紧。

1. 验证源站是否仍可绕过 CDN

先从历史 DNS、证书透明度记录、旧邮件头、同服务器其他域名等位置整理候选 IP,再用固定解析测试。不要直接访问 IP,因为虚拟主机和 TLS SNI 可能导致误判:

curl -vk --resolve  \
  

openssl s_client -connect 203.0.113.10:443 \
  -servername  </dev/null

如果返回的证书、页面特征或响应头与正式站一致,基本可以确认仍能绕过 CDN。修复时优先在安全组或主机防火墙中仅放行 CDN 官方出口网段,而不是依赖一个“秘密路径”或自定义请求头。

2. 收紧入口,但保留运维通道

以 nftables 为例,先建立白名单集合,再放行 80/443。配置前务必确认 CDN 网段完整,并单独保留管理 IP,避免把自己锁在机器外:

nft add table inet edge
nft 'add set inet edge cdn4 { type ipv4_addr; flags interval; }'
nft 'add element inet edge cdn4 { 192.0.2.0/24, 198.51.100.0/24 }'

nft 'add chain inet edge input { type filter hook input priority 0; policy accept; }'
nft add rule inet edge input tcp dport { 80, 443 } ip saddr @cdn4 accept
nft add rule inet edge input tcp dport { 80, 443 } drop

实际使用时不要照抄示例网段,应从 CDN 官方接口或公告同步,并做定时更新、语法校验和失败回滚。源站如果还承载回源健康检查,也要把对应探测网段加入集合。

3. 真实 IP 只能信任“直接上游”

常见错误是无条件记录 X-Forwarded-For 的第一个值。该头可以由客户端伪造,只有当 TCP 对端确实属于 CDN 网段时,才能接受 CDN 写入的真实 IP 头。

Nginx 可使用 realip 模块建立信任边界:

set_real_ip_from 192.0.2.0/24;
set_real_ip_from 198.51.100.0/24;

# 按 CDN 文档选择实际头名
real_ip_header X-Forwarded-For;
real_ip_recursive on;

如果 CDN 提供专用头,例如 CF-Connecting-IP 一类,通常比通用 XFF 更容易控制,但前提仍是源站只允许该 CDN 直连。应用框架中的 proxy trust 也应限制为明确的代理地址或跳数,不能简单设置为“信任全部代理”。

修改后可构造伪造头验证:经 CDN 请求时,日志中的客户端地址应由 CDN 重写;直连源站则应被网络层拒绝,而不是接受攻击者提交的头。

curl -H 'X-Forwarded-For: 1.1.1.1' 

curl -vk --resolve  \
  -H 'X-Forwarded-For: 1.1.1.1' \
  

4. 日志同时保留 TCP 对端、还原地址和转发链

排查阶段不要只记录 $remote_addr。启用 realip 后,它可能已经变成还原后的客户端地址;应额外保留 $realip_remote_addr,用于确认实际连接源站的是哪个 CDN 节点:

log_format edge_json escape=json
  '{"time":"$time_iso8601",'
  '"client_ip":"$remote_addr",'
  '"peer_ip":"$realip_remote_addr",'
  '"xff":"$http_x_forwarded_for",'
  '"host":"$host",'
  '"request":"$request",'
  '"status":$status,'
  '"request_id":"$request_id",'
  '"upstream_addr":"$upstream_addr",'
  '"upstream_time":"$upstream_response_time"}';

access_log /var/log/nginx/edge_access.log edge_json;

随后重点筛查两类异常:peer_ip 不属于 CDN 网段的成功请求,以及同一个 request_id 在 CDN、源站和应用日志中无法串联的请求。可先做简单聚合:

jq -r 'select(.status < 500) | .peer_ip' \
  /var/log/nginx/edge_access.log |
sort | uniq -c | sort -nr | head

grep -F '"request_id":"目标ID"' \
  /var/log/nginx/edge_access.log

如果确认曾长期暴露源站,除了封入口,还应回溯 WAF 未覆盖时段的访问日志,关注管理路径、上传接口、异常 Host、探测型 URI 和高频 4xx。最后再复测 IPv4、IPv6、80、443 以及备用域名;实际排查中经常只封了 IPv4,AAAA 记录或 IPv6 安全组仍允许直连。

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。