一句话版本
浏览器里最重要的安全规则是同源策略:协议、域名、端口三者完全相同才算"同一个源"(origin),不同源的页面互相读不到数据。 所以,把最敏感的功能放在一个单独的源上,等于给它建了一道防火墙:同一个网站其他角落出了 XSS 漏洞,也伸不进这个隔离区。这是安全工程里"缩小爆炸半径"的典型做法。
全文分三层:
- 通俗理解:同一栋楼和不同的楼
- 实际用处:大公司为什么把用户内容放在别的域名上
- 原理与工程要点:origin 和 site 的区别、隔离到什么程度才够
第一层:通俗理解
1.1 什么叫"同源"
| 网址 A | 网址 B | 同源吗 | 原因 |
|---|---|---|---|
https://example.com/a |
https://example.com/b |
是 | 只是路径不同 |
https://example.com |
http://example.com |
否 | 协议不同 |
https://example.com |
https://app.example.com |
否 | 域名不同 |
https://example.com |
https://example.com:8443 |
否 | 端口不同 |
同源的页面就像住在同一个房间:一个页面里的脚本可以随意读写另一个页面的内容、发请求、读 localStorage。不同源的页面则住在不同的房间,默认看不到彼此。
1.2 为什么要搬出去
假设一个网站的登录页、博客、论坛、钱包都放在 example.com 下面,只是路径不同。那么:
- 论坛有一个 XSS 漏洞(比如某个帖子能嵌入脚本);
- 这段脚本运行在
example.com这个源里; - 它就能读到同源的钱包页面,甚至悄悄改掉页面上的收款地址。
把钱包搬到 wallet.example.com,论坛的脚本就碰不到它了。漏洞仍然存在,但伤不到最值钱的东西。
第二层:实际用处
- 用户内容放在独立域名:Google 把用户上传的文件放在
googleusercontent.com,GitHub 把用户内容放在githubusercontent.com,原因都是:用户上传的 HTML 万一能执行脚本,也只能在一个"没有任何敏感数据"的源里运行。 - 管理后台独立:很多公司把管理后台放在单独的域名甚至内网,和面向用户的主站完全隔离。
- 支付页面:第三方支付表单通常以 iframe 的形式嵌在商家页面里,但 iframe 来自支付服务商自己的源,商家页面的脚本读不到里面的卡号。
- 加密货币:钱包和 DeFi 前端被篡改的事件(见《Bybit事件与前端完整性》)让人们越来越重视"签名页面必须是一个干净、隔离、可审计的源"。
Caloogy Wallet 也采用了这种做法:钱包运行在一个独立的源上。
第三层:原理与工程要点
3.1 同源策略管什么
| 行为 | 跨源时 |
|---|---|
| 读另一个窗口或 iframe 的 DOM | 禁止 |
读 fetch / XHR 的响应 |
默认禁止,除非对方用 CORS 明确允许 |
| 读 localStorage、IndexedDB | 各自独立 |
| 发出请求(表单、图片、链接) | 允许(这就是 CSRF 存在的原因,见《Cookie安全属性》) |
嵌入脚本 <script src> |
允许,而且脚本以嵌入者的身份运行(见《CSP与SRI》) |
3.2 origin 与 site 的区别
- origin:协议 + 完整域名 + 端口。
wallet.example.com和blog.example.com是不同 origin。 - site:协议 + "可注册域名"(eTLD+1)。它们属于同一个 site
example.com。
有两处是按 site 而不是按 origin 来算的,因此子域名隔离并不完美:
- Cookie:兄弟子站可以设置
Domain=example.com的 cookie,影响你的子站。对策是用__Host-前缀的 cookie。 - SameSite:从
blog.example.com发往wallet.example.com的请求被视为"同站",SameSite 挡不住。对策是服务端校验Origin头。
极端敏感的应用会干脆用一个完全不同的可注册域名,连 site 都分开。
3.3 隔离不只是浏览器的事
前端搬到单独的源,后端也要相应隔离,否则只是换了个门牌号:
- 独立的服务进程和系统用户、独立的数据库和密钥文件(见《最小权限与密钥管理》);
- 独立的会话,不和主站共享 cookie;
- 前端托管在一个与主站服务器无关的平台上:主站服务器被攻破时,攻击者也改不了这个页面。
3.4 更强的隔离头
| 响应头 | 作用 |
|---|---|
Cross-Origin-Opener-Policy: same-origin |
别的页面用 window.open 打开你后,拿不到你的窗口引用 |
frame-ancestors 'none'(CSP) |
禁止被嵌入 iframe,防点击劫持 |
Referrer-Policy: no-referrer |
跳出时不把当前网址(可能含敏感参数)泄露给对方 |
总结
| 层次 | 要点 |
|---|---|
| 直觉 | 同源 = 同一个房间;敏感功能要单独一个房间 |
| 价值 | 漏洞还在,但爆炸半径变小了 |
| 细节 | origin ≠ site:cookie 和 SameSite 按 site 算,要额外处理 |
| 配套 | 后端、会话、托管平台也要一起隔离 |