前端安全:XSS 和 CSRF 怎么防?
结论:XSS 防的是「脚本被注入执行」,核心是输出转义 + CSP + Cookie 加 HttpOnly;CSRF 防的是「别人借你的登录态发请求」,核心是 SameSite Cookie + CSRF Token + Origin 校验。两者防的是不同环节,必须同时做,只做其中一个等于没做。
XSS 和 CSRF 有什么区别?
一句话区分:XSS 是攻击者在你页面里「跑代码」,CSRF 是攻击者借你的浏览器「发请求」。
XSS(跨站脚本,Cross-Site Scripting)是攻击者把 <script> 之类的代码注入到页面里,这段代码在受害者浏览器上以本站身份执行,所以能读 document.cookie、偷 Token、改页面、发起任意请求。按注入位置分三类:存储型(恶意内容存进数据库,所有访客中招,危害最大)、反射型(放在 URL 参数里,诱导点击才触发)、DOM 型(前端 JS 直接把 location.hash 塞进 innerHTML,请求都没发出去就已经被利用)。
CSRF(跨站请求伪造,Cross-Site Request Forgery)不需要注入任何代码。它依赖一个事实:浏览器发同站请求时会自动带上 Cookie。攻击者只要让你在已登录状态下访问一个恶意页面,页面上一个隐藏表单 <form action="https://bank.com/transfer" method="POST"> 自动提交,你的 Cookie 就被一起带上去了。攻击者读不到响应(同源策略挡着),但转账这类操作本来也不需要读响应。
关键差异:XSS 能读能写,CSRF 只能写不能读。 所以 XSS 是更严重的问题——拿到 XSS 通常可以顺手绕过 CSRF 防护,因为 Token 也能被脚本读走。
XSS 怎么防?
结论:防 XSS 靠四层——上下文转义、富文本消毒、CSP 白名单、Cookie 加固,任何一层都不能替代另一层。
第一层,按上下文转义。 HTML 文本、HTML 属性、URL 参数、JS 字符串的转义规则都不同,不能一套 replace 走天下。好消息是主流框架默认帮你转:React 的 {value}、Vue 的 {{ value }} 都会自动做 HTML 转义。
第二层,盯住那几个「逃生舱口」。 React 的 dangerouslySetInnerHTML、Vue 的 v-html、原生 innerHTML 是唯一能绕过自动转义的口子。确实要渲染富文本时,先过一遍 DOMPurify:
const clean = DOMPurify.sanitize(dirtyHtml, { ALLOWED_TAGS: ['p','b','a','img'] });
服务端也要做同样的过滤,因为前端过滤只是体验,后端过滤才是安全边界。
第三层,上 CSP。 CSP(内容安全策略)通过响应头限制页面能加载和执行哪些脚本,是 XSS 的最后一道闸:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
注意不要为了省事加 'unsafe-inline',那等于没开。内联脚本改用 nonce:服务端每次响应生成随机值,script-src 'nonce-随机串',同时标签写 <script nonce="随机串">。
第四层,Cookie 加固。 给会话 Cookie 加 HttpOnly(JS 读不到 document.cookie)、Secure(只在 HTTPS 发送)、SameSite=Lax。这样即使真有 XSS,也偷不走会话 Cookie。
CSRF 怎么防?
结论:SameSite Cookie 挡住大部分场景,CSRF Token 是主力防线,Origin 校验兜底,三者叠加才稳。
第一,靠 SameSite。 Chrome 80(2020 年 2 月发布)起,未声明 SameSite 的 Cookie 默认按 Lax 处理,跨站的 POST 表单不再携带 Cookie。这一条已经挡住了最经典的 CSRF 攻击。但 Lax 不拦同站子域、也不拦 GET 导航,所以不能只靠它。
第二,上 CSRF Token。 服务端生成与会话绑定的随机 Token,前端提交时带上,服务端比对。放置方式两种:表单用隐藏字段 <input type="hidden" name="csrf_token" value="...">,AJAX 用请求头 X-CSRF-Token。Token 不要放在 URL 里,会进日志和 Referer。
第三,校验 Origin / Referer。 服务端检查请求头 Origin 是否在白名单内,不在就拒绝。注意:Origin 在部分旧浏览器上可能缺失,缺失时应该拒绝而不是放行。
第四,API 层加自定义头。 跨站表单只能发 application/x-www-form-urlencoded、multipart/form-data、text/plain 三种 Content-Type,发不了 application/json,也加不了自定义请求头。所以纯 JSON API + 强制 X-Requested-With 头 + CORS 白名单,天然就有一层保护。
第五,敏感操作二次验证。 改密码、转账、换绑手机号这类操作,要求重新输入密码或短信验证码,即使前面全被绕过也还有一道。
前后端各该做什么?
结论:前端的所有校验都是「用户体验」,后端的校验才是「安全」。
前端负责:用框架默认转义、不用 innerHTML、接入 DOMPurify、带上 CSRF Token、设置好请求头。但这些都能被绕过——攻击者可以直接 curl 你的接口。
后端必须负责:所有入库数据做校验和过滤、渲染时按上下文转义、下发 CSP 和 Cookie 安全属性、生成并校验 CSRF Token、校验 Origin、对高敏操作做二次验证。
这几个常见坑要避开
把 Token 存 localStorage。 XSS 一旦得手就能读走。会话凭证放 HttpOnly Cookie,CSRF 防护交给 Token 头。
以为 JSON API 天生免疫 CSRF。 只有强制校验 Content-Type 和自定义头时才成立,如果服务端也接受表单格式,防护就失效了。
只在前端过滤 <script>。 大小写变形、<img onerror>、javascript: 协议、SVG 事件都能绕过黑名单。用白名单,别用黑名单。
开了 CSP 但留着 unsafe-inline。 这条基本抵消了 CSP 对 XSS 的防护价值。
收尾一句:XSS 和 CSRF 的防护清单可以浓缩成八个动作——输出转义、富文本消毒、CSP 白名单、Cookie 加 HttpOnly/Secure/SameSite、CSRF Token、Origin 校验、JSON + 自定义头、敏感操作二次验证。前四个治 XSS,后四个治 CSRF,全部落在后端才真正生效。
原文链接:https://www.gj0.com/thread-434.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。