资讯详情

资讯详情

CSRF与XSS攻击原理及实战防护:从Token到CSP全解析

我就直接说重点了。Web 安全里 CSRF 和 XSS 这两兄弟基本是所有做 Web 开发的同行都绕不过去的坎。我见过太多团队要么把两者混为一谈要么在防护方案里只做了其中一项结果被攻击者钻了空子。这篇东西我不打算讲教科书上的定义而是基于我自己在多个项目里踩坑和实盘修复的经验把这两类攻击的原理、防护方案的选型逻辑以及落地时容易忽略的细节一次讲透。不管你是刚入门的前端新人还是已经带项目的后端负责人这篇文章里的方案和代码示例你都可以直接拿去参考。1. CSRF 与 XSS 的核心差异别再傻傻分不清1.1 两条完全不同的攻击路径很多人把 CSRF 和 XSS 放在一起讨论是因为它们都是利用浏览器发起恶意请求的攻击方式。但如果你把两者的攻击链拆开看就会发现它们其实是完全不同的两类问题。XSS跨站脚本攻击的本质是代码注入。攻击者想办法让你这个用户受害者的浏览器执行他写好的恶意脚本脚本能读取你页面里的数据、窃取你的 Cookie、篡改页面内容。这条链路的关键在于攻击者要在你的页面里跑代码。CSRF跨站请求伪造的本质是身份冒用。攻击者不需要在你的页面里执行任何脚本他只需要诱导你的浏览器向目标网站发出一条看似合法的请求比如转账、改密码、发帖而目标网站因为无法区分这条请求是不是你本人的真实意图就会照单执行。这条链路的关键在于攻击者要在目标网站信任浏览器这个身份。我用一个生活化的例子来解释。XSS 相当于有个小偷混进你家在你家里装了一个摄像头你的一举一动他都能看到CSRF 相当于小偷伪造了一张你签过字的委托书拿到银行去把你账户里的钱转走了而银行工作人员一看署名是对的就把钱给了小偷。这两个问题的防护思路也因此完全相反。XSS 要解决的是如何让浏览器不执行你页面里本不该被执行的脚本CSRF 要解决的是如何让服务器能验证请求确实来自用户的真实操作。如果你试图用一个方案同时解决两个问题大概率会两头都堵不住所以我们第一步先把它们拆开看。1.2 攻击面与真实危害对比搞清楚基本区别之后我们再来看实际的攻击面差异这决定了你需要在哪些环节重点投入。XSS 的影响范围通常是针对某个用户甚至所有用户。攻击者一旦成功注入脚本可以拿到用户的会话凭证Cookie、登录态 Token甚至直接以用户身份在当前页面上执行任意操作。更麻烦的是XSS 有一种存储型变体恶意脚本被存在服务器数据库里每个访问页面的用户都会中招——这本质上已经不是某个用户被攻击而是整个平台被污染。CSRF 的影响范围是针对某个用户在某个网站上的状态变更类操作。它的破坏力集中在写操作上转账、修改邮箱、删除数据、发布内容等。攻击者无法通过 CSRF 读取你的数据因为跨域请求在默认情况下无法读取响应内容但无法读取不代表无法造成破坏一次转账请求就能让受害者损失惨重。从实际危害来看CSRF 往往被低估因为它的攻击条件比较隐蔽——你只需要用户在不知不觉中访问了一个恶意页面即可不需要用户点击任何按钮用图片标签、表单自动提交都可以触发。而 XSS 的危害更直接一旦有人摸到了你的注入点往往意味着整个账户体系都暴露了。我在评估一个系统的安全状况时会先看这个系统是否存在 CSRF 防护、XSS 过滤是否覆盖了所有输入输出点。这两个问题的排查优先级都非常高但侧重点不一样CSRF 重点是检查所有状态变更接口是否校验了来源XSS 重点是检查所有渲染用户输入的位置是否做了转义。2. CSRF 防护实战从原理到落地的完整方案2.1 CSRF 为什么会发生同源策略的盲区先讲一个很多开发者容易混淆的点。浏览器是有同源策略的理论上跨域请求不应该被服务器信任那 CSRF 为什么还能成功关键在于同源策略限制的是读取跨域资源并不限制发送跨域请求。一个恶意页面完全可以向目标网站发起 POST 请求比如用表单、fetch 联调虽然这个页面无法读取目标网站返回的数据但它发出的请求会携带目标网站的 Cookie前提是 Cookie 设置了相应的域和路径服务器收到请求后根本不知道这个请求来自哪个页面它只知道这个请求带了合法会话。所以 CSRF 攻击成立有三个前提条件目标网站存在仅靠 Cookie 或默认凭证就批准操作的接口攻击者能构造出这个接口的合法参数受害者在攻击发生时正处于登录态。我在实际排查中遇到的绝大多数 CSRF 漏洞都出在接口只校验了参数语义没有校验请求来源这一点上。很多项目在内部联调时为了省事把接口设计成默认信任上线后又忘了补防护这就等于把家门钥匙挂在了门外。2.2 三大主流防护手段对比选型CSRF 防护方案主流主要有三类我分别说一下适用场景和优劣。防护方案核心思路优势劣势适用场景同步令牌模式Synchronizer Token服务端生成随机 Token校验请求中携带的 Token兼容性最好安全性高需要前后端配合Token 不能放 Cookie 里防抖逻辑要处理好所有类型的 Web 应用尤其是有表单提交的传统架构双重提交 Cookie 模式Double Submit Cookie请求参数中的 Token 与 Cookie 中的 Token 作比对不需要服务端存储适合无状态服务如果子域被攻破或存在子域 Cookie 覆盖可能失效前后端分离的单页应用、API 服务SameSite Cookie 属性浏览器限制跨站请求是否携带 Cookie零代码浏览器原生能力旧浏览器不识别对部分业务场景如跨域支付回跳有影响作为纵深防御的叠加项绝不能单独依赖我个人的选型建议是传统服务端渲染项目优先用同步令牌模式前后端分离的项目用双重提交 Cookie 模式做基础再叠加 SameSiteStrict 或 Lax 兜底。没有任何单一方案能 100% 防住所有场景多层叠加才是正解。2.3 实操案例基于 Token 方案的完整接入流程下面我以业界最常见的同步令牌模式为例演示一个前后端如何配合实现 CSRF 防护的完整流程。这里我用 Java Spring 的写法做展示核心逻辑在其他语言后端同样适用。第一步服务端生成 Token 并下发到页面// 使用 UUID 生成高强度随机 Token public String generateCsrfToken(HttpSession session) { String token UUID.randomUUID().toString().replace(-, ); session.setAttribute(CSRF_TOKEN, token); return token; }第二步前端在提交表单或发送 AJAX 请求时把 Token 从页面元数据中取出放入请求meta namecsrf-token content${csrfToken}// 以 axios 为例用请求拦截器统一注入 axios.interceptors.request.use(config { const token document.querySelector(meta[namecsrf-token]).getAttribute(content); config.headers[X-CSRF-Token] token; return config; });第三步服务端加拦截器统一校验请求中的 Tokenpublic boolean validateCsrfToken(HttpServletRequest request) { String sessionToken (String) request.getSession().getAttribute(CSRF_TOKEN); String requestToken request.getHeader(X-CSRF-Token); if (sessionToken null || requestToken null) { return false; } return MessageDigest.isEqual( sessionToken.getBytes(StandardCharsets.UTF_8), requestToken.getBytes(StandardCharsets.UTF_8) ); }这里有个非常关键的细节我必须强调Token 比较必须要用时间恒定的比较函数比如 Java 的MessageDigest.isEqual、Python 的hmac.compare_digest不能用普通的字符串equals。原因是普通字符串比较在遇到第一个不同字符时就会返回 false攻击者可以通过大量尝试来逐步推测 Token 的每一位这就是时间侧信道攻击。我见过不少项目因为图省事用了equals导致前面所有防护形同虚设。2.4 双重提交 Cookie 模式前后端分离项目的务实选择同步令牌模式在前后端分离架构里会遇到一个麻烦服务端不再有 Session无状态化改造Token 存哪里总不能把 Token 放缓存里增加复杂度吧。这时双重提交 Cookie 模式就派上了用场。它的核心逻辑是服务端不存 Token而是在用户登录成功后把一个随机 Token 同时写入 Cookie 和返回给前端。前端在后续每个写请求里把这个 Token 作为请求头或请求参数携带。服务端收到请求后比较 Cookie 里的 Token 和请求头里的 Token 是否一致一致则放行。// 登录响应示例 Set-Cookie: csrf_tokenabc123...; Path/; Secure; SameSiteLax // 前端在全局拦截器里手动获取 Cookie 并放入请求头 axios.interceptors.request.use(config { const rawCookie document.cookie; const tokenMatch rawCookie.match(/(?:^|; )csrf_token([^;])/); if (tokenMatch) { config.headers[X-CSRF-Token] tokenMatch[1]; } return config; });服务端校验逻辑也很简单取请求头里的X-CSRF-Token和请求中的 Cookie 值比对相等则通过。这种模式之所以有效是因为攻击者发起的跨站请求虽然会自动携带 Cookie但攻击者无法读取到 Cookie 的值也就没办法把这个值作为请求头发送出去。只要你的服务端严格校验Cookie 中的 Token 与请求体中的 Token 一致CSRF 就被拦住了。但这个方案有一个先天缺陷它防护的前提是攻击者无法在自己的恶意请求中给受害者设置同名 Cookie。正常情况下确实不行因为 Cookie 的域是固定的但如果你的站存在子域解析内网 IP、或者某些浏览器版本存在 Cookie 注入漏洞这个前提就可能被打破。所以使用双重提交 Cookie 模式时一定要把 Cookie 标记为Secure并且尽量把 Token 放在请求头而非请求参数里参数会被记录进日志有泄露风险。2.5 SameSite 属性别把它当主角但要无条件开启SameSite 是浏览器提供的一个 Cookie 属性用来控制跨站请求时是否携带 Cookie。它的三个值对应三种策略Strict完全禁止跨站携带 Cookie最安全但用户体验有损伤比如从外部链接点进来访问站点浏览器不会带 Cookie需要用户重新登录。Lax允许跨站页面上使用安全的顶级导航比如a标签跳转携带 Cookie但禁止 POST 表单、fetch 等跨站携带。这是目前主流浏览器的默认值兼顾了安全和体验。None允许所有场景携带但前提是必须同时设置Secure否则浏览器直接拒绝该 Cookie。我在真实项目里的建议是能用 Strict 就用 Strict不能用至少也得用 Lax。现在主流浏览器已经默认把未声明 SameSite 的 Cookie 视为 Lax所以即使你的代码里什么都没写新的攻击面也缩小了很多但老代码和低版本浏览器还是得靠显式配置兜底。// Spring Boot 中设置 SameSite 属性 Component public class SameSiteCookieConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true); } } // 实际设置 Cookie 时 response.addHeader(Set-Cookie, session_id sid ; Path/; HttpOnly; Secure; SameSiteStrict);注意SameSite是响应头Set-Cookie上的一个属性不是单独的响应头。我在审计一些项目时发现有人把它当作独立响应头添加这显然是没搞清楚底层用法。3. XSS 防护实战从输入端到输出端的层层设卡3.1 XSS 的三种常见形态与各自的攻防要点XSS 分为反射型、存储型和 DOM 型我在项目里分别处理过这三类问题它们的攻击路径差异很大防护侧重点也不同。反射型 XSS 是最常见的形态。恶意脚本通过 URL 参数提交到服务器服务器没有对参数进行转义就把它直接反射回到响应页面。用户点击了恶意构造的链接脚本就跟着页面加载执行。这种类型的攻击只对点击了恶意链接的人有效但因为可以伪装成正常链接诱导点击杀伤力依然不小。存储型 XSS 最危险。恶意脚本被提交到服务器数据库里任何访问包含该内容的页面的用户都会中招。评论区、个人简介、公告栏、富文本内容都是这类攻击的高发区。攻击者只需要提交一条带有script标签的评论之后所有浏览这条评论的人都会被脚本控制。DOM 型 XSS 和前面两种有本质区别它不经过服务器而是 URL 参数中的恶意代码被前端的 JavaScript 直接读取后通过 DOM 操作写入了页面。前端开发者使用innerHTML、document.write、eval等接口时最容易踩这个坑。针对这三种形态我的整体防护策略是反射型靠输出转义存储型靠输入校验加输出转义双层过滤DOM 型靠规范前端代码习惯加建立安全编码规范。你不可能把所有用户输入都过滤得一干二净有些场景天然要支持富文本所以输出转义才是最后一道可靠的防线。3.2 输出转义的核心逻辑与一套代码示例输出转义是 XSS 防护的基石。它的核心逻辑是在服务端渲染数据到 HTML 之前把,,,,这些特殊字符转成 HTML 实体形式这样浏览器在解析时只会把它们当作普通文本内容而不会当作 HTML 标签或 JavaScript 代码去执行。以 Java 后端为例我通常封装一个 HTML 转义的公共工具类public class HtmlEscape { public static String escape(String input) { if (input null) { return ; } StringBuilder escaped new StringBuilder(); for (char c : input.toCharArray()) { switch (c) { case : escaped.append(amp;); break; case : escaped.append(lt;); break; case : escaped.append(gt;); break; case : escaped.append(quot;); break; case \: escaped.append(#39;); break; default: escaped.append(c); break; } } return escaped.toString(); } }前端在渲染用户内容时同样不能直接把字符串塞进innerHTML。正确做法是使用textContent属性或者在框架层面使用自带转义的绑定语法。3.3 富文本场景下的 XSS 过滤演进路径很多业务场景没法对所有用户输入做全局转义最常见的就是富文本编辑器。用户要写带格式的帖子你总不能把他的加粗、超链接、图片全都过滤成纯文本。这时需要的是白名单机制。我在实操中整理了一套比较稳妥的富文本过滤策略第一步先用一个可靠的 HTML 解析器比如 Java 里的 Jsoup、Python 里的 Bleach把用户提交的内容解析成 DOM 树。第二步按照白名单过滤节点。只允许指定的标签如 p, br, strong, em, a, img, blockquote, code, pre其余标签全部移除。第三步对允许的标签做属性校验。只允许 href、src、class 等属性且还要校验这些属性的值。以 Jsoup 为例它的白名单节点过滤写起来非常简洁Whitelist whitelist Whitelist.relaxed(); whitelist.addTags(code, pre); whitelist.addAttributes(a, href, title); whitelist.addProtocols(a, href, http, https); whitelist.addProtocols(img, src, http, https); String cleanContent Jsoup.clean(userContent, whitelist);这个过程中有一个拦路虎字符串过滤完全不可靠。我见过很多项目尝试用正则或类似replace(script, )的方式做过滤结果攻击者只需要用scrscriptipt这种嵌套写法就能绕过。因为 HTML 解析是有上下文状态的正则根本不具备这个能力。我建议所有做过滤的团队都直接用成熟的 HTML 解析库不要自己写正则去匹配标签。另外要特别留意的点是富文本内容存储时可以保留 HTML但在传输到前端页面之前仍然建议在服务端把不属于白名单的 HTML 实体化。这样即使前端的过滤器被绕过服务端还留了一道闸门。3.4 CSP 与 HttpOnly两把不得不配的纵深防御武器CSP内容安全策略是浏览器提供的一层独立防御机制即使 XSS 注入成功了CSP 也能限制恶意脚本的执行。它的原理是告诉浏览器这个页面只允许从哪些来源加载和执行资源不在白名单内的脚本直接拒绝。我常用的 CSP 头配置Content-Security-Policy: default-src self; script-src self https://cdn.example.com; style-src self unsafe-inline; img-src self data:;这条策略的意思是脚本只允许从自身域和指定的可信 CDN 加载样式允许内联因为有些框架需要图片允许自身域和 base64 数据。这个配置能有效阻止eval、new Function等动态执行方式也能拦截所有来自外部域的脚本注入。CSP 的落地有一个实际难度很多老项目大量使用unsafe-inline内联脚本或者第三方库直接插入脚本标签一旦开启严格 CSP 会直接白屏。我的建议是分阶段推进先开report-only模式观察告警确认没有误伤后再切换到强制执行模式。HttpOnly 则是针对 XSS 窃取 Cookie 的专项对策。Cookie 一旦标记为HttpOnly浏览器对 JavaScript 就不可见了document.cookie无法读取即使攻击者成功注入了脚本也无法把 Cookie 偷走。response.addHeader(Set-Cookie, session_id sid ; HttpOnly; Secure; SameSiteStrict);这两个属性的组合作用很直观HttpOnly 切断窃取会话这个 XSS 攻击的核心目标CSP 切断加载外部恶意脚本这个路径。加了这两道锁之后即使你的转义代码有绕过的漏洞实际利用难度也大大提升。4. 常见问题与排查技巧实录这些坑我都替你踩过4.1 CSRF 防护失效的三个隐形原因我在做安全审计时经常碰到一种状况团队明明已经实现了 Token 校验接口却依然能被 CSRF 攻击。深挖之后发现基本都是下面三个原因。Token 存储在 Cookie 里是我见过最普遍的错误。开发者为了省事把所有流程都收敛到 Cookie当然也包括 CSRF Token。这种做法的问题在于跨站请求会自动携带 Cookie攻击者虽然读不到 Cookie 值但一个黑客构造的请求可以直接把 Cookie 里的值原样放回请求参数中——因为攻击者无法读取 Cookie所以 CSRF 的 Token 藏在 Cookie 里起不到验证作用这相当于防护直接失效。Token 必须放在服务端会话或内存里前端拿到后从请求头传递。对 GET 请求也做了防护但对 POST 请求漏掉了这也是常见错误。CSRF 的核心是高危操作它们的 HTTP 方法往往是 POST、PUT、DELETE。有的系统只对 POST 做 Token 校验却允许 DELETE比如删除操作不做校验攻击者扛着img标签就能发一个 DELETE 请求。请记住所有非幂等请求都必须校验GET 请求要确保是真正的只读操作不产生副作用。分布式环境没有共享会话存储也会导致 Token 校验逻辑失效。如果服务端用了多个实例用户第一次请求落在实例 A第二次请求落在实例 B而两个实例的 Session 不互通那么 Token 校验就会随机失败。解决方案是使用 Redis 做统一的会话存储使用spring-session或类似的组件。4.2 XSS 测试与代码审计的实操方法XSS 防护做到位没有光靠看代码不够我一般用三种方式组合测试。第一种是手动构造 payload 测试输入输出点。基本思路是在所有用户可输入的字段搜索框、用户昵称、留言板填入scriptalert(1)/script这类向量观察页面是否弹出弹窗。重点检查 URL 参数反射到页面的位置、评论区渲染位置、搜索结果的失败提示位置。第二种是使用自动化工具扫描。我用过的浏览器自动化工具可以直接分析页面结构自动在输入点填入 payload 并检测是否被转义。它的优势是可以覆盖大量输入点但单独使用漏报率很高因为 DOM 型 XSS 往往藏在 JavaScript 逻辑里自动化工具很难发现。第三种是代码审计这是最高效彻底的方式。我审计 XSS 时遵循一条主线找数据流——用户输入从哪里进来URL 参数、请求体、数据库读取数据到哪里渲染HTML 模板、innerHTML、eval、document.write。只要沿着这条线走任何未经过滤的点都会暴露出来。我建议团队在每个项目上线前至少走一遍这三步组合尤其不要跳过代码审计这一关。4.3 安全防护状态速查表与升级路径参考最后我整理了一份速查表方便你在做安全排查时直接对照也可以作为团队安全规范的技术底稿。检查项目达标状态不达标风险优先级所有写接口是否校验 CSRF Token全部校验Token 在非 Cookie 介质任意接口可被跨站伪造请求P0Top 级公共页面是否输出转义全部动态内容无原始 HTML 注入点存储型/反射型 XSS 感染所有访问用户P0富文本输入是否走白名单库过滤使用成熟解析器配置白名单黑名单/正则过滤可被绕过P0Cookie 是否设置 HttpOnly Secure会话 Cookie 必须设置已被注入的脚本直接窃取会话P1是否开启 CSP 策略建议启用可先 report-only 灰度XSS 成功后无第二层拦截P1动态执行函数使用情况业务代码无 eval/new FunctionDOM 型 XSS 面扩大P2从我个人的经验来看Web 安全防护没有一步到位的银弹。CSRF 和 XSS 都各有多层防护机制每层都有薄弱的地方但它们叠加之后攻击者的成本会呈指数上升。就像一把锁永远能防君子不能防决心够大的小偷但如果你装了三道锁加一个监控摄像头他大概率转头去下一家。最后分享一个我觉得很有价值的习惯每次安全加固之后都把所有当时的测试 payload 和绕过的思路记录下来整理成团队内部的攻击案例库。原因很简单——攻击手法在变团队也在流动唯有把这些实战经验沉淀下来才能让下一批经手代码的人少走弯路。我自己就在一次次复盘中意识到最好的安全方案一定来自对攻击者思路的深刻理解而不是背了多少条防护规范。希望这篇文章也能帮你把这两类攻击的底层逻辑看透在真实项目里做出更靠谱的防护决策。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →