⚖️ 本篇的边界(重要)
源站挖掘是取证中的合法技术手段,前提是已获得委托方授权,目标是找回自己委托资产的真实源站,用于还原完整请求链路。
本篇不涉及:对他人未授权的资产进行源站探测、绕过访问控制、扫描不属于委托范围的 IP 段。任何探测动作都必须在委托方明确的资产清单范围内进行,并遵守目标所在司法辖区的规定。
一、概述
CDN 给网站带来性能与防护,也给取证制造了一个具体的麻烦:日志里的 IP 既不是真实访问者,也不是服务器本身。
站点接了 CDN 之后,access_log 里记录的是边缘节点的地址。
这时候你手里的日志看起来是"完整的",实际上它缺了最关键的一段链路——那个域名背后,那台真实的服务器在哪里?
为什么非找到它不可:
| 若只有 CDN 侧证据 |
若找到源站 |
| 只能看到边缘节点日志 |
能看到完整请求,包括 CDN 未缓存的 POST |
| 无法确定哪些路径被真正访问过 |
能核对两边日志,找出绕过 CDN 的直连 |
| 无法判断源站是否还留着 WebShell |
能直接检查源站文件系统——这通常才是案件关键 |
| 攻击者绕 CDN 直连源站的攻击在 CDN 侧完全不可见 |
直连攻击完整记录在源站日志里 |
最关键的一点:CDN 缓存只加速 GET,POST 与动态请求通常回源。
攻击者上传 WebShell 用的正是 POST——这类请求在 CDN 侧往往只留下一行转发记录,真正的内容只有源站知道。
而绕过 CDN 直连源站的攻击,在 CDN 侧连一行记录都不会有。
在取证流程中的位置:本篇属于分析环的链路补全层,通常在文件层与流量层都卡住时启用——"日志里只有 CDN IP"是最常见的归因中断点。
上游依赖 Nginx 与 Apache 配置取证 对 $remote_addr 语义与 proxy_set_header 的分析。
下游直接接上源站侧的全部工作(取证工件地图、WebShell 排查、流量分析),见 3.5 第 5 步。
涉及的证据形态:
| 形态 |
位置 / 字段 |
取证价值 |
| DNS 解析链 |
dig 的 CNAME 链、+noall +answer |
★★★★★ CNAME 指向 *.cdn.* / *.edge.* 即已接 CDN |
| HTTP 响应头 |
server、via、x-cache、cf-ray、age |
★★★★ 二次确认 |
| CT log |
证书透明度日志,公开可查、带时间戳、可哈希固定 |
★★★★★ 源站回溯的主力手段 |
| 未接 CDN 的子域 |
测试站、静态站、旧站、API 站的 A 记录 |
★★★★★ 最常见的突破口 |
| 历史 DNS |
接入 CDN 之前的解析记录 |
★★★★ 直连攻击的原始来源 |
| 配置错误枚举 |
未设 Host 白名单、遗留测试域名、mail/origin 等命名习惯 |
★★★ |
| 源站指纹 |
自定义错误页、序列号、静态资源哈希、响应头顺序 |
★★★★ 验证候选的关键 |
| CDN 与源站日志的差集 |
两侧记录的比对结果 |
★★★★★ 找出绕过 CDN 的直连 |
🔴 取证价值最高的一条判据:同一域名下未接 CDN 的子域常常直接暴露源站。 运维往往只把主站接入 CDN,测试站、静态站、旧站、API 站常常遗漏。但同时要记住边界:找到了源站 IP 不等于它就是攻击入口——IP 归属 CDN 厂商、或属于托管在同一台机器上的其他客户,都需要单独认定,见 4.1 归属判断。
这个域名背后哪台是真实服务器;哪些子域没走 CDN;源站上是否还留着后门;绕 CDN 直连源站的攻击有没有发生过;源站 IP 上还托管着哪些其他域名;CDN 配置是否留下了绕过路径(未设 IP 白名单、未配 SNI 校验等)。
CDN 边缘节点缓存了什么、什么时候缓存的——这需要 CDN 厂商侧的配合或控制台权限。
未签发过证书的源站(CT log 里查不到)只能靠其他路径。
源站机器的内部情况?找到 IP 只是拿到入口,文件系统证据必须在授权范围内获取。
源站 IP 上其他客户域名的内容——超出委托范围就是越界,见 4.3 范围与授权边界。
内容边界(本篇的红线,已在篇首声明):源站挖掘是取证中的合法技术手段,前提是已获得委托方授权,目标是找回自己委托资产的真实源站,用于还原完整请求链路。本篇不涉及对他人未授权资产的源站探测、绕过访问控制、扫描不属于委托范围的 IP 段。任何探测动作都必须在委托方明确的资产清单范围内进行,并遵守目标所在司法辖区的规定。
读者前提:需要 DNS 解析原理与 CNAME 链结构、TLS 握手与证书基本概念(SNI、证书 SAN)、HTTP 响应头语义、以及反向代理的基本模型。
反向代理如何在日志里隐藏真实 IP 的机制见 第 1 篇 的 2.3 节。
建议先按 3.1 第 1 步 确认 CDN 状态再往下——如果本来就没接 CDN,本篇的多数手段不适用。
二、核心原理
2.1 CDN 是怎么改变日志的
一个典型的 CDN 架构:
用户 ──► CDN 边缘节点 ──► 源站服务器
(缓存 GET) (所有请求最终都会到这里)
| 请求类型 |
CDN 行为 |
边缘节点日志 |
源站日志 |
GET 静态资源,缓存命中 |
不回源 |
有记录 |
无记录 |
GET 动态页面,未命中 |
回源 |
有记录 |
有记录 |
POST(登录、上传、执行) |
总是回源 |
有记录 |
有完整记录 |
| 攻击者直连源站(绕过 CDN) |
不经过 CDN |
无记录 |
有完整记录 |
由此推出两条取证推论:
- 源站日志是"必然存在"的——只要 CDN 正常工作并转发,源站一定有日志。所以拿到源站 IP 几乎一定能拿到更完整的日志。
- CDN 侧日志里的 4xx/5xx 与状态码可能被 CDN 自己改写(CDN 配置了错误页缓存时尤其明显),不能把 CDN 日志的状态码当作源站真实响应。
2.2 CDN 识别:DNS 链
最可靠的第一手判据是 DNS 解析结果。
接入 CDN 时,域名通常不再是 A 记录直指服务器,而是 CNAME 到 CDN 厂商的域名。
dig +short digiforensics-site
# cdn.digiforensics-edge.example.net. ← CNAME,说明接了 CDN
dig digiforensics-site CNAME +short
# cdn.digiforensics-edge.example.net.
# 完整链路
dig digiforensics-site +noall +answer
# digiforensics-site. 300 IN CNAME cdn.digiforensics-edge.example.net.
# cdn.digiforensics-edge.example.net. 60 IN A 203.0.113.20
| 现象 |
判断 |
CNAME 指向 *.cdn.* / *.edge.* / *.cloudfront.net / *.alicdn.com 等 |
接了 CDN |
| A 记录直接指向单个 IP,且该 IP 属于云厂商 |
可能是独立主机,也可能直连云厂商网络 |
| A 记录返回多个 IP |
可能 CDN,也可能是DNS 轮询(多台服务器)——需看 IP 归属 |
| 域名下部分子域有 CNAME、部分没有 |
典型的"CDN 接入不全",未接 CDN 的子域往往直达源站 ★ |
🔴 取证价值最高的一条:同一个域名下,未接 CDN 的子域常常直接暴露源站。 运维往往只把主站接入 CDN,测试站、静态站、旧站、API 站常常遗漏。这往往是突破口。
2.3 CDN 识别:HTTP 响应头
curl -sI https://digiforensics-site/ | head -30
| 响应头 |
指向 |
Server: cloudflare |
Cloudflare |
X-Cache: HIT / MISS / EXPIRED |
多种 CDN(Cloudflare、阿里云等) |
Via: 中出现 1.1 vegur(蓝汛)、ali-swift-* |
阿里云 CDN |
X-Swift-Cache |
又拍云等 |
X-Cache-Lookup |
快网等 |
Age: 3600 |
任意 CDN,表示该响应已缓存 3600 秒 ★ |
cf-ray、cf-cache-status |
Cloudflare |
X-Cache: TCP_HIT |
腾讯云 |
Timing-Allow-Origin |
各类 CDN |
| 完全没有上述头 |
可能是直连源站(★ 这本身就是源站线索) |
即使厂商换了自定义头,Age: <秒数> 通常还在。
2.4 CT log(证书透明度日志)—— 源站回溯的主力手段 ★
这是当前最实用的源站回溯方法,本节重点展开。
原理
自 2017 年前后起,主流 CA 与主流浏览器逐步把公开受信任 TLS 证书的 CT 记录作为签发前提(Apple、Google、Mozilla 的证书政策要求、以及 2018 年 4 月起 Chrome 对 EV 证书的硬性要求,都推动了这件事)。
全球有多个公开的 CT log 汇总这些记录,例如 Google CT Log,以及 crt.sh 这类按域名聚合的第三方检索服务。
因此在绝大多数公开网站场景下,一张受信任证书的签发几乎必然留下 CT 痕迹。
但“几乎必然”不等于“绝对覆盖”——自签证书、内网私有 CA 签发的证书、以及历史上绕过该要求的签发,都可能查不到。
关键点:签发证书时必须提交所有 SAN 域名的公钥信息到 CT log,且无法事后删除。
由此得到取证的用途:
任何曾经为这个域名签发过证书的人,其证书上的所有域名都被公开记录了。 包括:源站常用的 origin.example.com、direct.example.com、内部测试域名、绑定在同一个 IP 上的其他客户域名。
取证的直接价值:
- 得到可用于回溯 IP 的域名线索 —— CT 本身不提供 IP,但它给出的
origin. direct. test. old. dev. admin. 这类非常规子域名,是后续做 DNS 历史查询、证书下载比对、目标验证的高价值抓手。IP 最终要靠 DNS 历史、SNI 探测和日志交叉验证才能确定。
- 得到源站专用子域名 ——
origin. direct. oss. static-origin. 这类命名往往是内部用词。
- 得到未公开的业务域名 —— 同一客户的其他站点。
- 得到时间线 —— CT 记录含"证书签发时间",可与入侵时间对照。
这四条里,第 1 条最关键——它不是直接给出答案,而是把搜索空间从"整个互联网"缩小到"十几个名字"。
⚠️ 重要局限(必须在报告里写明):
- CT log 只记录证书,不记录 IP 指向。 它告诉你"这个域名签过证书",但域名现在解析到哪,CT 不管。
- 未使用公开 CA 证书的源站(自签证书、IP 直连无证书)不会出现在 CT log。
- CT 记录与“未来”无关,回溯靠的是 log 自身保留的历史。 公开 CT log 自 2013 年前后开始运行并长期保留条目,因此历史证书在多数情况下可以查到;但保留策略、log 迁移与部分早期条目缺失都可能导致查不到,必须以实际查询结果为准。
- 查询需要联网。在离线或隔离的内网取证环境中无法使用,必须提前在有网环境采集并固定为证据。
实操查询
# 方式一:crt.sh(按域名聚合的 CT 记录)
# 注意:URL 中的 %25. 是对子域名通配的转义写法
curl -s 'https://crt.sh/?q=%25.digiforensics-site&output=json' \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('\
'.join(sorted({n for r in d for n in r['name_value'].split()})))"
# 方式二:Google CT Log API(官方原始记录)
curl -s 'https://transparencyreport.google.com/transparencyreport/api/v3/httpsreport/ct/v1/logs' -o /dev/null # 探活
# 实际查询通常通过官方前端;离线取证时建议直接采集 crt.sh 结果并固定
# 方式三:证书指纹反查(已知证书序列号时)
# crt.sh 支持 ?sha256=<证书指纹> 查询同一张证书的所有 SAN
查询结果要做的事:
# 1) 落盘为证据(★ 关键:CT 是联网查询结果,必须当场固定,否则不可复现)
mkdir -p /evidence/ct
date -u '+%Y-%m-%dT%H:%M:%SZ' > /evidence/ct/collected-at.txt
curl -s 'https://crt.sh/?q=%25.digiforensics-site&output=json' \
-o /evidence/ct/crt-$(date -u +%Y%m%dT%H%M%SZ).json
sha256sum /evidence/ct/crt-*.json | tee /evidence/ct/hashes.txt
# 2) 提取去重域名
python3 -c "
import json,glob,sys
f=sorted(glob.glob('/evidence/ct/crt-*.json'))[-1]
d=json.load(open(f))
names=sorted({n.strip() for r in d for n in r.get('name_value','').splitlines()})
for n in names: print(n)
" | tee /evidence/ct/domains.txt
# 3) ★ 筛出"源站味"的子域
grep -iE 'origin|direct|backend|server|node|host|panel|admin|^\*|raw|src|oss|upstream|inner' /evidence/ct/domains.txt
# 4) 逐个解析,看哪些没走 CDN
while read -r d; do
[ "$d" = "*.digiforensics-site" ] && continue
ip=$(dig +short "$d" A | head -1)
[ -n "$ip" ] && echo "$d -> $ip"
done < /evidence/ct/domains.txt
筛出来的典型结果:
| 域名 |
解析结果 |
判断 |
www.digiforensics-site |
CNAME → CDN |
走 CDN |
origin.digiforensics-site |
A → 198.51.100.24 |
★ 源站 |
api.digiforensics-site |
A → 198.51.100.24 |
源站(同 IP) |
old.digiforensics-site |
无解析 |
历史域名,可能已下线 |
2.5 其他源站挖掘路径
历史 DNS
域名在接入 CDN 之前的解析记录,或曾经被解析到源站的那段时期,都可能留在历史数据库里。
# 查权威 NS 与 SOA(先确认 DNS 托管方)
dig +short digiforensics-site NS
dig digiforensics-site SOA +noall +answer
# digiforensics-site. 3600 IN SOA ns1.provider.example. admin.provider.example. 2024031701 3600 600 1209600 3600
# ^ SOA serial 号变化能反映 DNS 变更次数
# TTL 变化会暴露"曾做过切换"
dig digiforensics-site +noall +answer
取证要点:SOA 的 serial 号(如 2024031701)是管理员手动递增的,每一次 DNS 记录变更都会让它变大。
serial 异常大意味着历史上做过大量变更——包括可能的解析切换。
配置错误导致的源站暴露(最常见的泄露途径)
| 错误类型 |
表现 |
检查方法 |
| 子域未接入 CDN |
origin. direct. test. dev. 直连源站 |
CT log + 子域枚举 |
| 邮件记录 |
DNS MX 记录通常必须指向真实服务器 |
dig MX digiforensics-site |
| 非标准端口服务 |
源站 8888/8080 等端口未做防护 |
端口核对(限委托范围) |
| 源站 IP 上部署的其他域名 |
同 IP 承载多站 |
CT log + 证书反查 |
| 错误页泄露 |
源站 4xx/5xx 页面暴露了 upstream 地址 |
直连源站观察(授权范围内) |
| **源码与配置泄 |
|
|