资讯动态

前端安全实战:彻底吃透XSS与CSRF攻击原理及防范方案

发布时间:2026/10/18 6:18:10 来源:尧图企业网站定制
1. 为什么前端开发者必须吃透XSS和CSRF做Web开发这些年我见过太多团队在功能上线时信心满满结果被一个简单的弹窗脚本或者一条伪造的转账请求打得措手不及。XSS和CSRF这两个名字很多人第一次听到是在面试八股文里第二次听到就是线上出事故的时候。这篇文章不打算给你背概念而是把我自己踩过的坑、修过的漏洞、以及最终沉淀下来的一套防范思路完整地摊开来讲。XSS全称跨站脚本攻击核心逻辑是攻击者把恶意脚本注入到网页里让它在其他用户的浏览器上执行。CSRF全称跨站请求伪造核心逻辑是攻击者借用用户已经登录的身份在用户不知情的情况下发起恶意请求。两者经常被混在一起讨论但它们的攻击路径和防范手段完全不同。一个像是往你家信箱里塞了一封带毒的信另一个像是有人拿着你的门禁卡去刷开了你家的门。这篇文章适合谁看如果你是刚入行的前端或后端开发者正在搭建自己的第一个有用户系统的项目那这篇内容能帮你从一开始就把安全底座打牢。如果你已经工作了两三年但安全这块一直是“知道个大概”那正好借这个机会把知识体系补全。如果你是小团队的技术负责人这里面的方案和配置可以直接拿去用不需要再花时间做二次调研。我个人的经验是安全这东西不出事的时候觉得离自己很远出了事就是大事。与其等被扫出漏洞再连夜修复不如在写代码的时候就养成肌肉记忆。下面我会从攻击原理、实操复现、防范方案、排查技巧四个维度把XSS和CSRF彻底讲透。2. XSS攻击的三种类型与核心原理拆解2.1 存储型XSS最危险的那种存储型XSS是我认为危害最大的一类。攻击者把恶意脚本提交到服务器的数据库里比如评论区、用户昵称、文章内容这些地方。只要其他用户访问了包含这段数据的页面脚本就会自动执行。整个过程不需要用户做任何额外的操作打开页面就中招。我举个实际遇到过的场景。之前有个项目做的是内部论坛用户可以在个人签名里写任意内容。有个测试同学在签名里填了一段脚本结果所有点进他主页的人都会弹窗。当时大家还觉得挺好玩但如果这段脚本换成窃取登录凭证的代码后果就不堪设想了。存储型XSS的可怕之处在于它的传播是自动化的一个人中招所有访问相关页面的人都跟着中招。从技术层面看存储型XSS的形成需要两个条件同时满足第一后端没有对用户输入做充分的过滤或转义第二前端在渲染这段数据时直接把它当作HTML或JavaScript执行了。很多开发者觉得后端做了参数校验就万事大吉但校验和转义是两回事。校验是判断输入是否符合预期格式转义是确保输出到页面上时不会被当作代码执行。两者缺一不可。2.2 反射型XSS钓鱼链接的常客反射型XSS的特点是恶意脚本不在数据库里而是藏在URL参数中。攻击者构造一个带有恶意参数的链接诱导用户点击。用户点开后服务器把参数值直接拼接到返回的HTML里脚本就执行了。这种攻击方式特别适合配合钓鱼场景。比如攻击者发一条消息说“点击链接查看你的订单详情”链接看起来是正常站点的域名但参数里藏了脚本。用户一看域名没问题就点了结果脚本在页面上执行把用户的登录凭证或者页面上的敏感信息发送到攻击者控制的服务器。反射型XSS和存储型XSS的核心区别在于持久性。反射型是一次性的链接失效或者参数被清理后攻击就结束了。但它的成功率往往不低因为很多人对域名有天然的信任感看到熟悉的域名就放松了警惕。我在做安全测试的时候经常用反射型XSS来验证一个站点的输入过滤是否到位因为它最容易构造和验证。2.3 DOM型XSS纯前端的隐形杀手DOM型XSS比较特殊它的恶意脚本完全在前端执行不经过服务器。攻击者通过修改URL的片段标识或者查询参数触发前端JavaScript中的危险操作比如innerHTML、document.write、eval这些。这类XSS的隐蔽性很强因为服务器端看到的请求可能是完全正常的日志里也查不到异常。问题出在前端代码把不可信的数据直接当代码执行了。我见过一个典型的例子前端从URL的hash里读取一个参数然后用innerHTML把它插入到页面中。攻击者只需要构造一个带脚本的hash值就能让页面执行任意代码。DOM型XSS的防范重点在于前端开发者自己要有安全意识。后端帮不了你因为数据根本没到后端。每一个把用户可控数据插入到DOM的操作都需要问自己一句这段数据会不会被当作代码执行如果会那就必须做转义或者使用安全的API。2.4 三种XSS类型的对比与攻击链路类型恶意代码存储位置触发方式持久性典型场景存储型服务器数据库用户访问页面自动触发持久评论区、用户资料、文章内容反射型URL参数用户点击恶意链接一次性搜索框、错误提示页、跳转页DOM型URL片段或前端数据前端JS执行时触发一次性单页应用、前端路由、动态渲染理解这三种类型的区别关键在于搞清楚恶意代码从哪里来、在哪里执行、影响范围有多大。存储型影响面最广反射型最容易被用于钓鱼DOM型最容易被前端开发者忽略。在实际项目中这三种类型往往不是孤立存在的一个输入点可能同时存在多种XSS风险。3. CSRF攻击的运作机制与真实危害3.1 CSRF到底是怎么发生的CSRF的核心在于“身份借用”。用户登录了某个网站浏览器里保存了该网站的会话凭证通常是Cookie。攻击者诱导用户访问一个恶意页面这个页面里藏着一个向目标网站发起的请求。浏览器在发送请求时会自动带上目标网站的Cookie服务器一看Cookie有效就认为是用户本人操作于是执行了请求。整个过程用户完全无感知。你只是打开了一个看起来人畜无害的页面后台就已经以你的名义发了一封邮件、转了一笔账、或者改了一个密码。CSRF之所以能成功根本原因在于浏览器对Cookie的自动携带机制以及服务器端仅凭Cookie来判断用户身份。我经常用一个类比来解释CSRF你拿着公司的门禁卡进了大楼门禁系统只认卡不认人。攻击者不需要偷你的卡只需要在你刷卡进门之后跟着你混进去就行了。你的卡还在你身上但门已经开了。3.2 一个典型的CSRF攻击场景还原假设有一个银行转账页面请求格式是POST /transfer参数是to账户amount金额。用户登录后Cookie里保存了会话标识。攻击者构造一个页面里面放一个自动提交的表单目标地址就是转账接口收款账户和金额都填好了。用户访问这个页面表单自动提交浏览器带上Cookie服务器执行转账。这个场景里攻击者不需要知道用户的密码也不需要拿到用户的Cookie。他只需要知道转账接口的URL和参数格式然后诱导用户访问恶意页面就行了。这也是为什么CSRF防范不能只靠“用户不点陌生链接”这种安全意识教育必须从技术层面做防护。3.3 GET型与POST型CSRF的差异早期的CSRF攻击多利用GET请求比如在img标签的src里放一个转账链接。用户打开页面图片加载失败但请求已经发出去了。这种攻击方式简单粗暴但随着越来越多的接口改用POSTGET型CSRF的成功率在下降。POST型CSRF需要构造一个自动提交的表单或者用JavaScript发起请求。虽然实现上稍微复杂一点但危害是一样的。而且很多开发者有一个误区觉得POST接口就天然免疫CSRF这是完全错误的。只要服务器端没有做额外的校验POST接口一样可以被伪造请求。还有一种容易被忽略的情况是JSON接口。有些开发者觉得接口只接受Content-Type: application/json的请求表单提交不了所以是安全的。但实际上攻击者可以用JavaScript的fetch或者XMLHttpRequest来发起JSON请求只要浏览器允许跨域携带CookieCSRF依然可能发生。3.4 CSRF与XSS的关系和区别很多人会把XSS和CSRF搞混或者觉得防住了XSS就等于防住了CSRF。这两个攻击的目标和路径完全不同。XSS是让恶意脚本在目标网站上执行攻击的是网站的用户CSRF是借用用户的身份向目标网站发起请求攻击的是网站本身。但两者之间有一个重要的关联如果网站存在XSS漏洞那么CSRF防范措施可能会失效。因为攻击者可以通过XSS读取页面上的CSRF Token然后构造带有正确Token的请求。所以安全防护是一个整体不能只堵一个口子。从防范角度看XSS的重点是输入过滤和输出转义CSRF的重点是请求来源验证和Token校验。两者需要分别处理不能互相替代。4. 防范XSS的实操方案与代码级细节4.1 输入过滤第一道防线怎么设输入过滤的核心原则是“白名单优于黑名单”。黑名单是列出所有不允许的字符或标签但攻击者总能找到绕过的方法。白名单是只允许特定的字符或格式其他一律拒绝。比如用户名字段只允许字母、数字和下划线那就用正则表达式严格限制而不是去过滤script这样的标签。在实际操作中我通常会在后端做两层处理。第一层是格式校验确保输入符合业务预期的格式。第二层是转义把特殊字符转换成HTML实体。比如转成lt;转成gt;转成amp;。这样即使输入里包含了脚本标签输出到页面上也只是一段普通文本。需要注意的是输入过滤不能只在后端做。前端也要做基本的校验提升用户体验但前端的校验可以被绕过所以后端必须再做一遍。我见过一些项目只在前端做了校验结果攻击者直接用工具发请求前端校验形同虚设。4.2 输出转义不同上下文的处理策略输出转义是防范XSS最有效的手段之一但关键在于“根据上下文选择正确的转义方式”。同样是用户输入的数据放在HTML标签里、放在属性里、放在JavaScript代码里、放在URL里需要的转义规则是不一样的。放在HTML文本节点里需要转义、、、、这些字符。放在HTML属性里除了这些字符还需要注意属性值要用引号包裹。放在JavaScript代码里需要对特殊字符做Unicode转义。放在URL参数里需要用encodeURIComponent做编码。我个人的习惯是在模板引擎里默认开启自动转义。比如用主流的前端框架时默认的插值语法就是转义的只有明确使用危险API时才会执行原始HTML。后端模板引擎也类似尽量使用自动转义的配置减少手动处理的环节。4.3 Content Security Policy的配置与落地CSP是我认为目前最有效的XSS缓解手段之一。它的原理是通过HTTP响应头告诉浏览器只允许加载和执行来自特定来源的脚本。即使攻击者成功注入了脚本浏览器也会因为CSP的限制而拒绝执行。一个基础的CSP配置大概是这样的Content-Security-Policy: default-src self; script-src self https://trusted-cdn.example.com; style-src self unsafe-inline; img-src self data: https:; object-src none; base-uri self; form-action self这个配置的意思是默认只允许加载同源资源脚本只允许从同源和指定的CDN加载样式允许同源和内联样式图片允许同源、data URI和HTTPS来源禁止加载插件对象限制base标签和表单提交的目标地址。配置CSP的时候我建议先用Content-Security-Policy-Report-Only模式跑一段时间收集违规报告确认没有误伤正常功能后再正式启用。直接上强制模式很容易把页面搞崩特别是那些用了大量内联脚本的老项目。4.4 HttpOnly Cookie与前端安全API的使用HttpOnly是Cookie的一个属性设置了之后JavaScript就无法通过document.cookie读取这个Cookie。这对于防范XSS窃取会话凭证非常有效。即使攻击者成功注入了脚本也拿不到关键的会话Cookie。设置方法很简单在服务端返回Set-Cookie头的时候加上HttpOnly标记Set-Cookie: sessionIdabc123; HttpOnly; Secure; SameSiteLax除了HttpOnlySecure属性确保Cookie只在HTTPS连接中传输SameSite属性限制跨站请求携带Cookie这三个属性配合使用能大幅提升会话安全性。在前端代码层面要避免使用innerHTML、outerHTML、document.write这些危险API来插入用户可控的数据。如果确实需要动态渲染HTML可以使用textContent来设置纯文本或者用成熟的转义库先处理数据。现在很多前端框架默认就是安全的比如React的JSX会自动转义Vue的模板插值也会转义但使用dangerouslySetInnerHTML或者v-html的时候就要格外小心。5. 防范CSRF的完整技术方案5.1 CSRF Token的生成与校验流程CSRF Token是目前最主流、最可靠的防范手段。它的核心思路是服务器生成一个随机Token嵌入到页面表单或者请求头里。服务器在处理请求时校验这个Token是否有效。攻击者因为同源策略的限制无法读取到Token所以无法构造出带有正确Token的请求。Token的生成要用密码学安全的随机数生成器不能用手写的伪随机函数。Token要和用户的会话绑定但不要直接使用会话ID作为Token。每次用户登录或者会话刷新时应该重新生成Token。Token可以放在表单的隐藏字段里也可以放在自定义的请求头里。校验的时候要注意Token必须和当前用户的会话匹配不能只校验Token是否存在。我见过一些实现只检查了Token字段有没有值没有验证它是否属于当前用户这种校验等于没做。另外Token比较的时候要用时间安全的比较函数避免时序攻击。5.2 SameSite Cookie属性的正确设置SameSite是Cookie的一个属性用来控制跨站请求时是否携带Cookie。它有三个值Strict、Lax和None。Strict最严格任何跨站请求都不带Cookie。Lax是默认值允许顶级导航的GET请求携带Cookie但POST请求和iframe里的请求不带。None则允许所有跨站请求携带Cookie但必须同时设置Secure属性。对于大多数网站来说Lax是一个比较平衡的选择。它既能防范大部分CSRF攻击又不会影响正常的跨站跳转体验。比如用户从搜索引擎点进来Lax模式下Cookie会正常携带用户保持登录状态。但如果是从第三方页面发起的POST请求Cookie就不会被带上。设置方法是在Set-Cookie头里加上SameSite属性Set-Cookie: sessionIdabc123; HttpOnly; Secure; SameSiteLax需要注意的是SameSite并不能完全替代CSRF Token。有些老旧的浏览器可能不支持SameSite属性而且某些特殊场景下仍然需要Token来做双重保险。我的建议是SameSite和Token同时使用形成纵深防御。5.3 验证请求来源Referer与Origin检查除了Token和SameSite检查请求的Referer和Origin头也是一种辅助手段。Origin头在跨站请求时会带上请求发起方的源信息服务器可以校验这个源是否在允许列表里。Referer头则包含了更详细的来源页面地址。这种方式的局限性在于Referer和Origin头可以被某些隐私设置或者代理工具修改或移除。所以它只能作为辅助校验不能作为唯一的防范手段。我通常会把Origin检查作为第一层过滤如果Origin不在允许列表里直接拒绝请求。如果Origin缺失再走Token校验流程。在实际配置中要注意允许列表的维护。如果网站有多个域名或者子域名需要把这些都加到允许列表里。同时要小心通配符的使用过于宽松的匹配规则会让检查形同虚设。5.4 关键操作的二次验证设计对于特别敏感的操作比如修改密码、转账、删除账号我强烈建议加上二次验证。二次验证可以是重新输入密码、输入短信验证码、或者使用身份验证器应用生成的动态码。这样即使CSRF攻击成功发起了请求攻击者也会卡在二次验证这一步。二次验证的设计要注意用户体验和安全的平衡。不是所有操作都需要二次验证只有那些一旦被恶意执行就会造成严重后果的操作才需要。比如浏览页面、搜索内容这些只读操作就不需要。而涉及资金变动、权限变更、数据删除的操作加上二次验证是值得的。从实现角度看二次验证的校验逻辑要独立于普通的会话校验。不能因为用户已经登录了就跳过二次验证否则CSRF攻击依然可以绕过。二次验证的凭证要有时效性用一次就失效防止被重放。6. 实战排查常见漏洞场景与修复记录6.1 富文本编辑器带来的XSS隐患富文本编辑器是XSS的重灾区。用户可以在编辑器里输入各种格式的内容包括链接、图片、表格等。如果后端没有对提交的HTML做严格的过滤攻击者就可以在内容里嵌入脚本标签或者事件属性。我处理过的一个案例是编辑器允许用户插入图片图片的src属性没有做限制。攻击者把src设置成javascript:alert(1)用户点击图片时就触发了脚本。修复方案是对所有属性值做白名单校验只允许http、https、data这些安全的协议。另一个常见问题是onerror、onload这类事件属性。攻击者可以在图片加载失败时触发脚本。修复方法是在服务端用成熟的HTML过滤库把不在白名单里的标签和属性全部移除。不要自己写正则表达式来过滤HTMLHTML的语法太灵活了正则很难覆盖所有情况。6.2 单页应用中的DOM型XSS排查单页应用因为大量使用前端路由和动态渲染DOM型XSS的风险比较高。我在排查这类问题时会重点看几个地方路由参数是怎么读取和使用的动态组件是怎么渲染的有没有使用innerHTML或者类似的危险API。一个典型的漏洞场景是前端从URL的查询参数里读取一个redirect地址然后直接用location.href跳转。攻击者可以把redirect参数设置成javascript:alert(1)用户点击链接后就执行了脚本。修复方法是校验跳转地址的协议只允许http和https。还有一个场景是前端从URL的hash里读取数据然后用innerHTML渲染到页面上。这种写法在早期的单页应用里很常见。修复方法是改用textContent或者用转义函数先处理数据。如果确实需要渲染HTML要用白名单过滤库。6.3 接口层面的CSRF漏洞发现与修补接口层面的CSRF漏洞往往比较隐蔽因为接口本身可能看起来很正常。我在做安全测试时会重点关注那些没有Token校验、没有SameSite保护、没有Origin检查的接口。特别是那些用Cookie做身份认证的接口如果只依赖Cookie来判断用户身份就存在CSRF风险。修补的方法根据接口的类型有所不同。对于表单提交类的接口加上CSRF Token是最直接的方案。对于API接口可以要求请求头里带自定义的认证Token而不是依赖Cookie。对于内部接口可以加上IP白名单或者内网访问限制。需要注意的是修补CSRF漏洞的时候要考虑到兼容性。如果接口已经被多个客户端使用突然加上Token校验可能会导致老客户端无法正常工作。我的做法是先加上Origin检查作为过渡同时通知客户端升级等所有客户端都支持Token后再强制校验。6.4 常见问题速查表问题现象可能原因排查方法修复方案页面弹出异常弹窗存在存储型或反射型XSS检查用户输入点的输出转义对输出做上下文转义开启CSP用户反馈账号被操作存在CSRF漏洞检查敏感接口的Token校验加上CSRF Token和SameSite单页应用路由跳转异常DOM型XSS检查URL参数的使用方式避免危险API校验跳转协议CSP报告大量违规CSP配置过严或存在注入分析违规报告来源调整CSP策略或修复注入点Cookie被窃取缺少HttpOnly属性检查Set-Cookie头加上HttpOnly和Secure这张表是我在实际排查中总结出来的基本上覆盖了大部分常见情况。遇到安全问题时先对照这张表做初步判断然后再深入分析具体原因。7. 把安全防护融入日常开发流程7.1 代码审查中的安全检查清单安全不应该是一次性的工作而应该融入到日常的代码审查中。我在团队里推行了一份安全检查清单每次提交代码涉及用户输入输出或者敏感操作时都要对照清单过一遍。清单的内容包括用户输入是否做了格式校验和转义输出到页面的数据是否根据上下文做了转义是否使用了危险的DOM API敏感接口是否有CSRF Token校验Cookie是否设置了HttpOnly、Secure、SameSiteCSP策略是否覆盖了新的资源来源这份清单不需要很长但每一条都要落到实处。我见过很多团队的安全规范写了几十页但开发的时候没人看。反而是这种简短的清单贴在代码审查模板里每次提交都能看到效果更好。7.2 自动化安全测试工具的引入人工审查总有遗漏的时候引入自动化工具做补充是很有必要的。静态代码分析工具可以在代码提交时扫描潜在的安全问题比如未转义的输出、危险的API调用、缺失的CSRF Token等。动态扫描工具可以在测试环境里模拟攻击验证防护措施是否有效。我通常会在CI流程里加两个环节一个是静态扫描用工具分析代码里的安全模式另一个是依赖检查看看项目引用的第三方库有没有已知的安全漏洞。这两个环节不需要太长时间但能拦住大部分低级错误。需要注意的是自动化工具会有误报。不要因为工具报了一个问题就盲目修改要先确认是不是真的漏洞。同样工具没报问题也不代表一定安全人工审查依然不能省。7.3 安全响应头的一站式配置除了CSP还有几个安全响应头也值得配置。X-Content-Type-Options: nosniff可以防止浏览器猜测内容类型避免把非脚本文件当作脚本执行。X-Frame-Options: DENY或者SAMEORIGIN可以防止页面被嵌入到iframe里防范点击劫持。Strict-Transport-Security可以强制浏览器使用HTTPS连接。这些响应头可以在Web服务器或者应用框架层面统一配置。比如在Nginx里加几行配置或者在应用启动时设置全局中间件。配置一次全站生效性价比很高。我一般会把这些安全响应头做成一个配置模板新项目直接复用。这样不需要每次重新调研也不会因为遗漏某个头而导致防护不完整。7.4 团队安全意识的持续培养工具和流程能解决大部分问题但最终写代码的还是人。团队的安全意识决定了防护的上限。我会定期在团队内做安全分享不讲太理论的东西就讲最近遇到的真实案例或者行业内新出的漏洞。让大家知道安全不是纸上谈兵而是实实在在会影响用户和业务的事情。新同学入职的时候我会让他先看一遍安全检查清单然后在代码审查中重点关注他的提交。前几个月可能会慢一点但养成习惯之后安全就会变成一种本能。写代码的时候自然会想到转义、校验、Token这些关键词而不是等出了问题再补救。安全这件事投入产出比其实很高。前期多花一点时间做防护后期就能少花很多时间做应急。而且随着经验积累这些防护动作会越来越熟练最终变成开发流程中自然而然的一部分。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑