网络与安全
前端安全入门:XSS、CSRF 与 CSP
前端最常遇到的两类攻击是什么原理,日常开发怎样防范,再加上一层 CSP 作为兜底。
XSS:别人的脚本跑在了你的页面上
跨站脚本攻击(XSS)的本质,是把用户输入当成代码执行了。一旦脚本运行在你的页面上,它就能以当前用户的身份做任何事。
按来源可以分为三类:
| 类型 | 恶意内容在哪里 | 例子 |
|---|---|---|
| 存储型 | 服务器数据库 | 评论、昵称里的内容被原样显示 |
| 反射型 | URL 参数,由服务器拼进页面 | 搜索结果页显示搜索词 |
| DOM 型 | URL 等,由前端脚本写入页面 | 把 location.hash 放进 innerHTML |
怎样防范 XSS
核心原则:把数据当数据,不要当 HTML。
// 危险:内容会被解析成 HTML
comment.innerHTML = userInput;
// 安全:内容只会被当成文本
comment.textContent = userInput;React、Vue 这些框架默认会转义插值,已经挡住了大部分问题。要特别注意绕过转义的写法:dangerouslySetInnerHTML 和 v-html。如果确实需要渲染富文本,要用 DOMPurify 这样经过验证的库来清洗,不要自己写正则。
链接也可能是攻击入口。 javascript: 开头的地址在点击时会执行脚本,用户提供的链接要校验协议:
function safeUrl(input) {
try {
const url = new URL(input, location.origin);
return ['http:', 'https:'].includes(url.protocol) ? url.href : '#';
} catch {
return '#';
}
}CSP:出了问题时的最后一道防线
内容安全策略(CSP)通过响应头告诉浏览器,只能从哪里加载和执行资源。即使有内容被注入了页面,内联脚本也不会执行。
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'| 指令 | 作用 |
|---|---|
default-src 'self' | 默认只允许同源资源 |
script-src 'self' | 只执行同源的脚本文件,内联脚本和 eval 都会被禁止 |
object-src 'none' | 禁用插件内容 |
base-uri 'none' | 防止注入 <base> 标签改写相对地址 |
frame-ancestors 'none' | 禁止被别的页面嵌入,防止点击劫持 |
如果项目中有内联脚本,可以用 nonce 或 hash 精确放行。尽量不要加 'unsafe-inline',否则 CSP 对 XSS 的防护效果会大打折扣。
老项目直接上线 CSP 很可能把页面弄坏。可以先用 Content-Security-Policy-Report-Only 响应头,它只报告违规而不会真正拦截,确认没问题后再切换。
CSRF:借用户的身份发请求
跨站请求伪造(CSRF)的原理是:用户登录了 A 网站,又打开了恶意的 B 网站。B 网站让浏览器向 A 发送请求,浏览器会自动带上 A 的 Cookie,A 以为是用户自己的操作。
注意,这种攻击不需要读取响应,所以 CORS 挡不住它。主要的防御手段有:
1. 设置 SameSite Cookie。 这是最简单有效的手段:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/| SameSite 值 | 跨站请求时 |
|---|---|
Strict | 完全不带 Cookie,从外部链接点进来也需要重新登录 |
Lax | 只有顶层导航的 GET 请求才带,是常用的平衡选择 |
None | 都带,必须同时设置 Secure |
Chromium 会把没声明的 Cookie 按 Lax 处理,但不是所有浏览器都如此,应该明确写出来。
2. 使用 CSRF Token。 服务端生成随机值,放在页面或接口中,提交时校验。攻击者的网站读不到这个值,也就无法伪造请求。
**3. 校验 Origin 请求头。** 服务端检查请求是否来自自己的域名,可以作为额外的防线。
Token 存哪里
- **
localStorage**:写起来方便,但 XSS 能直接读走。 - **
HttpOnlyCookie**:脚本读不到,可以防止被窃取,但要配合防 CSRF。
需要注意的是,HttpOnly 只能防止 Cookie 被读走。如果页面已经存在 XSS,攻击者依然可以在页面内以用户的身份发送请求。所以防止 XSS 始终是第一位的。
一份简单的检查清单
- 不把用户输入放进
innerHTML,富文本先清洗。 - 用户提供的链接校验协议。
- 登录 Cookie 设置
HttpOnly、Secure、SameSite。 - 修改数据的接口不使用 GET,并做好 CSRF 防护。
- 配置 CSP,至少先以 Report-Only 模式运行。
- 依赖定期更新,用
npm audit检查已知漏洞。 - 权限校验一定放在服务端,前端的判断只是界面逻辑。