跳转到帖子

咖啡馆实战:网站加速链路下的源站暴露与权限边界排查

精选回复

发布于

在网站建设和服务器运维中,CDN、WAF、反向代理与源站通常会形成多层访问链路。实际授权测试里,比较常见的问题不是某个单点漏洞,而是加速层、源站访问控制和应用权限判断之间没有对齐:公网域名经过 CDN 后访问正常,但源站 IP 仍然可以直接访问;应用又错误信任客户端提交的 X-Forwarded-For,导致后台限制、审计记录和安全策略出现偏差。

下面以“网站使用 CDN 加速,源站为 Nginx + 应用服务,后台仅允许办公网访问”的场景为例,说明如何在授权范围内定位问题、验证影响并修复。测试目标是确认访问边界和日志完整性,不涉及数据导出、持久化或破坏性操作。

一、场景与测试边界

  • 业务域名: CDN 或云 WAF 对外提供服务。
  • 源站:公网 IP 为 203.0.113.20,Nginx 监听 80/443,后端应用监听本机 127.0.0.1:8080。
  • 管理入口:/admin/,设计要求仅办公网 10.20.0.0/16 可访问。
  • 授权范围:指定域名、指定源站 IP 和测试时间窗口;只验证 HTTP 状态、响应头、固定的健康检查接口,不读取业务数据。

验证前应记录授权域名、源站地址、测试账号、允许的请求速率和禁止操作。尤其不要把“发现源站 IP”直接等同于“可以继续访问任意内部服务”,后续每一步都应重新确认范围。

二、问题定位:先确认请求到底经过了哪一层

第一步是比较正常域名访问和直接访问源站时的响应差异。建议使用固定的公开页面或健康检查接口,并保留响应头、状态码和时间,不要批量抓取站点内容。

#!/usr/bin/env bash
set -u

DOMAIN='
ORIGIN='203.0.113.20'
PATH='/healthz'

printf '%s\n' '--- access through CDN/WAF ---'
curl -skS --max-time 8 -D - " -o /dev/null

printf '%s\n' '--- direct access to origin with Host header ---'
curl -skS --max-time 8 --resolve "${DOMAIN}:443:${ORIGIN}" \
  -D - " -o /dev/null

printf '%s\n' '--- direct IP without expected Host ---'
curl -skS --max-time 8 -D - " -o /dev/null

重点观察以下结果:

  • 源站直连是否返回与正常域名相同的业务内容,而不是默认站点或拒绝页面。
  • 响应头中是否泄露源站标识、内部版本、上游名称或调试信息。
  • 直接访问源站时,TLS 证书、虚拟主机和 SNI 是否配置正确。
  • CDN 回源请求是否使用固定的回源标识,源站是否只允许 CDN 出口网段访问。

--resolve 会让 curl 将域名解析到指定地址,同时保留原始 Host 和 TLS SNI,比直接请求 IP 更接近真实的源站访问场景。测试时不要依赖修改本机 hosts 后进行大量请求,避免误伤其他环境。

三、常见根因一:源站把任意来源都当成可信代理

很多应用会根据 X-Forwarded-For 判断客户端 IP,Nginx 也可能通过 real_ip_header 改写日志中的地址。如果没有限制“哪些上游代理可以提交该请求头”,攻击者可以自行构造一个看似来自办公网的地址,造成两类问题:

  • 应用层 IP 白名单被绕过。
  • 访问日志记录的客户端地址不可信,事件调查无法还原真实来源。

在 Nginx 中,set_real_ip_from 必须只填写实际 CDN/WAF 的出口网段,不能配置为全网,也不能仅因为某个请求头名称看起来可信就直接使用。

# /etc/nginx/conf.d/realip.conf
# 示例网段,实际值必须以厂商公布并经过变更审批的回源网段为准
set_real_ip_from 198.51.100.0/24;
set_real_ip_from 2001:db8:100::/48;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

log_format main_secure '$remote_addr original=$realip_remote_addr '
                       'xff="$http_x_forwarded_for" '
                       'request_id=$request_id '
                       'host=$host request="$request" '
                       'status=$status bytes=$body_bytes_sent '
                       'upstream_status=$upstream_status '
                       'request_time=$request_time';

这里保留了 $realip_remote_addr 和原始 X-Forwarded-For,便于发生争议时区分“经过可信代理还原的地址”和“客户端提交的请求头”。配置修改后先执行语法检查,再平滑加载:

sudo nginx -t
sudo systemctl reload nginx
sudo nginx -T | sed -n '/realip.conf/,+20p'

不同 CDN 对真实客户端地址的请求头名称可能不同,例如 True-Client-IP 或厂商自定义字段。不能直接照抄配置,应以链路抓包、厂商文档和实际回源请求为准,并确保该请求头在源站防火墙层面只接受来自可信回源地址的流量。

四、常见根因二:后台只做了应用层限制,没有做服务器层限制

后台入口如果只在 Java、PHP 或 Node.js 代码里判断客户端地址,通常还会受到代理头解析、IPv4/IPv6 表示方式和多级代理链的影响。更稳妥的做法是将限制下沉到源站反向代理,再由应用做第二层校验。

server {
    listen 443 ssl http2;
    server_name 

    # 默认拒绝后台入口
    location ^~ /admin/ {
        allow 10.20.0.0/16;
        allow 192.0.2.10;
        deny all;

        proxy_set_header Host $host;
        proxy_set_header X-Request-ID $request_id;
        proxy_pass 
    }

    location = /healthz {
        access_log off;
        proxy_pass 
    }

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Request-ID $request_id;
        proxy_pass 
    }
}

在授权验证中,可以只访问 /admin/ 首页,观察允许网段和非允许网段的状态码是否符合预期。不要尝试登录、修改配置或提交表单。若测试环境必须验证应用层权限,应使用专门的低权限测试账号,并提前准备可回滚的测试数据。

需要特别检查 IPv6。只配置 IPv4 白名单而未配置 IPv6 监听或防火墙规则,可能导致相同域名解析到 IPv6 时出现一条未受保护的访问路径。可用以下命令核对:

dig +short A 
dig +short AAAA 
ss -lntp | grep -E ':(80|443|8080)\b'
sudo nft list ruleset

五、验证“源站暴露”是否已经形成实际风险

源站 IP 可访问本身不一定是高危问题,关键要看是否绕过了原有的安全控制。建议按以下顺序进行低影响验证:

  1. 访问固定健康检查接口,确认源站是否响应。
  2. 访问公开页面,比较 CDN 与源站的缓存、认证和安全响应头。
  3. 仅对后台目录发送一次 GET,比较办公网和非办公网的状态码。
  4. 使用测试请求头验证应用是否错误信任客户端提供的代理信息,但不进入后台、不提交数据。
  5. 在 Nginx、WAF、应用和 CDN 日志中按请求 ID 对齐记录,确认请求来源是否可追溯。

可以将结果整理成“路径—入口—认证—网络限制—日志字段”的矩阵。例如:

路径CDN 访问源站直连非办公网访问日志可追溯性
/healthz200应拒绝或仅允许回源网段不适用应有 request_id
/admin/应由源站 ACL 控制应拒绝403记录真实连接地址与代理链
/200不应绕过 WAF 策略按业务规则记录 Host、状态码、耗时

六、日志排查:判断是否存在绕过和误报

发现问题后,不能只看应用日志。应同时核对 CDN、源站访问日志、WAF 告警、系统防火墙和应用认证日志。重点关注同一时间段内的以下模式:

  • 同一用户会话在 CDN 日志中来自公网地址,但应用日志中显示为办公网地址。
  • 源站 IP 收到大量带正确 Host 的请求,而 CDN 日志中没有对应记录。
  • 请求头中出现多个逗号分隔的代理地址,应用却只记录了最左侧或最右侧地址。
  • 后台返回 200,但 WAF 或源站 ACL 没有对应的放行记录。
# 按时间、状态码和请求路径初步筛选 Nginx 日志
sudo awk '$0 ~ /request="GET \/admin\// && $0 ~ /status=200/' \
  /var/log/nginx/access.log | tail -n 100

# 检查是否有源站 IP 直连但没有对应 CDN 标识的请求
sudo grep -E 'host=www\.example\.test' /var/log/nginx/access.log \
  | grep -v 'cdn-request-id' | tail -n 100

# systemd 环境下查看最近的拒绝记录
sudo journalctl -u nginx --since '2 hours ago' \
  | grep -Ei 'denied|forbidden|access forbidden'

生产环境不建议只依赖文本 grep。更可靠的做法是将 CDN、WAF、Nginx 和应用日志统一写入日志平台,使用 request_id 或 W3C Trace Context 进行关联,并为源站直连、代理头异常、后台拒绝和证书不匹配建立告警。

七、修复建议与复测标准

  • 网络层:在安全组、防火墙或负载均衡层限制源站入站来源,仅允许 CDN/WAF 回源网段和必要的运维出口;关闭不必要的公网端口。
  • 虚拟主机层:为未知 Host 配置默认拒绝站点,避免通过 IP 或任意 Host 命中业务虚拟主机。
  • 代理头层:只信任来自明确代理网段的真实客户端地址,并保留原始连接地址用于审计。
  • 应用层:后台同时使用身份认证、角色授权和网络边界控制,不能把 IP 白名单当成唯一认证因素。
  • 日志层:统一记录 request ID、连接地址、还原后的客户端地址、原始代理头、Host、状态码和上游状态,避免记录密码、令牌等敏感内容。
  • 配置变更:执行 nginx -t 后再 reload,并在变更后从 CDN、办公网、普通公网和 IPv6 四个视角复测。

复测至少应满足:源站直连不再绕过 CDN/WAF 策略;非办公网访问后台得到明确拒绝;伪造代理头不会改变源站 ACL 和应用权限判断;CDN、源站与应用日志能够通过同一请求 ID 对齐;健康检查、缓存和证书配置不因修复而中断。

总结

网站加速链路中的安全问题,通常不是“有没有 CDN”这么简单,而是要看 CDN、源站、应用和日志对客户端身份的定义是否一致。授权测试时应从固定接口和低影响请求开始,先确认入口关系,再验证权限边界,最后用日志还原完整链路。对于源站暴露、代理头信任和后台访问控制这类问题,网络层拒绝、Nginx 访问控制、应用授权和审计关联需要同时落地,单独修补其中一层往往仍会留下绕过路径。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意

这类场景我一般会把排查拆成两条线:一条确认“源站是否能被绕过加速链路访问”,另一条确认“源站信任边界有没有放错位置”。不要只看域名解析当前是不是 CNAME 到加速厂商,很多暴露点在历史记录、证书、备用域名和回源配置里。

1)先确认有没有直接命中源站的路径

# 当前解析链路
dig +short example.com
dig +short 
dig +trace 

# 常见备用域名 / 回源域名
for d in origin old api admin test dev static img; do
  dig +short $d.example.com
done

# 如果怀疑某个 IP 是源站,用 Host 头直接打
curl -k -I --resolve  
curl -k -I -H 'Host:  

判断是不是源站不要只看状态码,可以对比这些特征:

  • Server、X-Powered-By、自定义 header 是否和加速链路不同;
  • 错误页样式是否暴露 Nginx/Apache/Tomcat 默认页;
  • 同一路径下响应时间、缓存头、TLS 证书 SAN 是否异常;
  • 访问管理后台、上传目录、健康检查接口是否只有源站能看到。

2)源站入口应只允许加速厂商回源 IP

如果源站公网 IP 必须保留,至少在安全组 / 防火墙 / Nginx 三层做限制。Nginx 可以这样做兜底:

server {
    listen 443 ssl;
    server_name 

    # 只允许 CDN 回源 IP 段,示例需替换成真实厂商网段
    allow 203.0.113.0/24;
    allow 198.51.100.0/24;
    deny all;

    location / {
        proxy_pass 
    }
}

更推荐在云安全组层面限制,因为 Nginx 规则改错时仍可能漏;源站如果还有 80、8080、8443、管理端口,也要一起收口,不要只关 443。

3)重点看真实 IP 透传的信任边界

很多权限边界问题不是源站暴露本身,而是应用错误信任了 X-Forwarded-For。如果后端用这个头判断内网、白名单、管理员 IP,就很容易被伪造。

Nginx 正确做法是只信任加速节点传来的真实 IP:

# 仅信任 CDN 回源 IP 段
set_real_ip_from 203.0.113.0/24;
set_real_ip_from 198.51.100.0/24;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

同时应用层不要直接取用户可控 header:

# 不建议
client_ip = request.headers.get("X-Forwarded-For")

# 更稳妥:由反向代理清洗后传入,应用只取可信字段
client_ip = request.headers.get("X-Real-IP") or request.remote_addr

可以用下面方式验证是否存在伪造风险:

curl -I  \
  -H 'X-Forwarded-For: 127.0.0.1' \
  -H 'X-Real-IP: 127.0.0.1'

如果加这些 header 后权限、限流、后台访问结果发生变化,基本就是边界判断放错了。

4)补充几个容易漏的暴露面

  • 证书透明日志里出现过 origin.example.com、admin.example.com 这类域名;
  • 邮件、对象存储、监控回调里泄露过源站 IP;
  • 历史 DNS 记录仍能查到旧 A 记录;
  • IPv6 没接 CDN,但 AAAA 记录直连源站;
  • 加速只覆盖主站,API、后台、静态资源域名没覆盖。

最后建议落地成一个最小闭环:源站安全组只放行 CDN 回源 IP,应用不要信任客户端传入的 IP 头,后台接口额外加鉴权或 VPN/零信任入口,不要把“经过加速链路”当成权限边界。这样即使源站 IP 被枚举出来,也很难形成实际绕过。

补一条我觉得很容易漏的:源站暴露本身是一回事,真正危险的是应用把“来自加速链路的标识”当成了权限边界。 可以重点测这几类头是否被源站直接信任:
X-Forwarded-For
X-Real-IP
X-Forwarded-Proto
Forwarded
CF-Connecting-IP
True-Client-IP
X-Client-IP
如果后台、管理接口、限流、风控是按这些头判断客户端 IP,一旦源站可直连,就可能被伪造。 可以这样对比测试:
# 走正常加速链路
curl -i 

# 如果已确认某个 IP 疑似源站,直连源站并伪造来源 IP
curl -k -i --resolve example.com:443:1.2.3.4 \
  -H 'X-Forwarded-For: 127.0.0.1' \
  -H 'X-Real-IP: 127.0.0.1' \
  

# 测试内网白名单绕过
curl -k -i --resolve example.com:443:1.2.3.4 \
  -H 'X-Forwarded-For: 10.0.0.1' \
  
如果响应从 403 变成 200,或者返回内容差异明显,就说明权限判断放错层了。 源站侧建议不要无条件信任这些头,只信任来自 CDN / 反代节点的转发头。Nginx 可以这样收口:
# 只信任 CDN 回源 IP 段
set_real_ip_from 203.0.113.0/24;
set_real_ip_from 198.51.100.0/24;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

# 非 CDN 来源直接拒绝
geo $from_cdn {
    default 0;
    203.0.113.0/24 1;
    198.51.100.0/24 1;
}

server {
    listen 443 ssl;
    server_name example.com;

    if ($from_cdn = 0) {
        return 403;
    }

    location / {
        proxy_pass 
    }
}
更稳一点的做法是源站再加一层“回源密钥头”,CDN 回源时加固定 header,源站校验:
# 源站 Nginx
map $http_x_origin_token $valid_origin {
    default 0;
    "your-random-token" 1;
}

server {
    listen 443 ssl;

    if ($valid_origin = 0) {
        return 403;
    }

    location / {
        proxy_pass 
    }
}
注意这个只能作为补充,不能替代源站防火墙放行 CDN IP。因为密钥一旦在日志、错误页、调试接口里泄露,就失效了。 排查时我一般还会看这几个点:
  • 安全组 / 防火墙是否仍然对公网开放 80、443、8080、8443、9000 等端口;
  • 后台路径是否只在 CDN WAF 上拦截,源站本身没有鉴权;
  • 限流是否只在 CDN 层做,直连源站是否不受限制;
  • 应用日志里真实客户端 IP 是否异常,比如大量请求都变成 CDN 节点 IP,说明 real_ip 没配好;
  • 健康检查、灰度域名、老负载均衡地址是否绕过了主链路策略。
源站最终目标不是“藏起来就行”,而是即使 IP 被知道,也只能被 CDN / 反代按预期访问,权限判断也不依赖客户端可伪造的 header。

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

最近浏览 0

  • 没有会员查看此页面。

SiteMap HACKHAT © 2026 All rights reserved.