Rucky 前端笔记

网络与安全

前端安全入门:XSS、CSRF 与 CSP

前端最常遇到的两类攻击是什么原理,日常开发怎样防范,再加上一层 CSP 作为兜底。

XSS:别人的脚本跑在了你的页面上

跨站脚本攻击(XSS)的本质,是把用户输入当成代码执行了。一旦脚本运行在你的页面上,它就能以当前用户的身份做任何事。

按来源可以分为三类:

类型恶意内容在哪里例子
存储型服务器数据库评论、昵称里的内容被原样显示
反射型URL 参数,由服务器拼进页面搜索结果页显示搜索词
DOM 型URL 等,由前端脚本写入页面把 location.hash 放进 innerHTML

怎样防范 XSS

核心原则:把数据当数据,不要当 HTML。

JavaScript
// 危险:内容会被解析成 HTML
comment.innerHTML = userInput;

// 安全:内容只会被当成文本
comment.textContent = userInput;

React、Vue 这些框架默认会转义插值,已经挡住了大部分问题。要特别注意绕过转义的写法:dangerouslySetInnerHTML 和 v-html。如果确实需要渲染富文本,要用 DOMPurify 这样经过验证的库来清洗,不要自己写正则。

链接也可能是攻击入口。 javascript: 开头的地址在点击时会执行脚本,用户提供的链接要校验协议:

JavaScript
function safeUrl(input) {
  try {
    const url = new URL(input, location.origin);
    return ['http:', 'https:'].includes(url.protocol) ? url.href : '#';
  } catch {
    return '#';
  }
}

CSP:出了问题时的最后一道防线

内容安全策略(CSP)通过响应头告诉浏览器,只能从哪里加载和执行资源。即使有内容被注入了页面,内联脚本也不会执行。

HTTP
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。 这是最简单有效的手段:

HTTP
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 能直接读走。
  • **HttpOnly Cookie**:脚本读不到,可以防止被窃取,但要配合防 CSRF。

需要注意的是,HttpOnly 只能防止 Cookie 被读走。如果页面已经存在 XSS,攻击者依然可以在页面内以用户的身份发送请求。所以防止 XSS 始终是第一位的。

一份简单的检查清单

  • 不把用户输入放进 innerHTML,富文本先清洗。
  • 用户提供的链接校验协议。
  • 登录 Cookie 设置 HttpOnly、Secure、SameSite。
  • 修改数据的接口不使用 GET,并做好 CSRF 防护。
  • 配置 CSP,至少先以 Report-Only 模式运行。
  • 依赖定期更新,用 npm audit 检查已知漏洞。
  • 权限校验一定放在服务端,前端的判断只是界面逻辑。

延伸阅读