ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

XSS与CSRF本质区别:从攻击原理到修复实践的全面对比

XSS与CSRF本质区别:从攻击原理到修复实践的全面对比 说实话做了这么多年安全我最常被问到的问题不是“某个漏洞怎么利用”而是这类带着困惑的对比题XSS 和 CSRF 到底有什么本质区别这两个名字在热搜上绑在一起出现不是没道理的——它们都出现在 OWASP Top 10 里都跟 Web 前端有关扫出来都是“高危”修起来都容易改错方向。更麻烦的是很多刚入行的同学把 XSS 的修复方案套到 CSRF 上或者反过来最后被测试人员打回来说“漏洞没修干净”。我见过不少项目卡在验收阶段就是因为这两类问题被混为一谈。所以这篇我不打算只丢定义我想从一个实操者的角度把这两个漏洞的攻击链路、触发条件、修复要点全部拆开用对比的方式讲清楚。你会看到它们本质上的差异到底在哪里一个往你的页面里“塞东西”另一个借你的身份“发请求”。能分清这句话后面所有细节都顺了。这个内容适合三类人正在学 Web 安全的初学者、负责修复外部漏洞报告的开发同学、以及做安全测试需要写报告的同学。我尽量不用太悬的词遇到关键概念会展开讲读完你至少能做到拿到一个漏洞描述能快速判断是 XSS 还是 CSRF并知道该往哪个方向修。1. 先建立直觉XSS 和 CSRF 到底在“骗”谁1.1 用两个场景秒懂概念先说 XSS。假设你打开了一个论坛有人在一篇帖子的评论区发了一段内容这段内容里混着一段浏览器能识别的脚本。论坛程序没做过滤直接把这帖子的评论原样显示给了所有浏览的人。你的浏览器加载到这个页面时那段脚本就在你的浏览器里执行了。它可以读取当前网站的 Cookie、篡改页面内容、把你输入的表单数据悄悄传到另一个域名下。整个过程里攻击者骗的是浏览器浏览器以为脚本是网站自己生成的于是乖乖执行了。用户本身什么都没做错只是浏览了一个被“污染”的页面。再说 CSRF。假设你登录了某银行的网上转账系统浏览器里还留着登录凭证Cookie。这时候你被诱导打开了一个恶意网站这个网站的页面里藏着一个指向银行转账接口的请求。浏览器向银行服务器发起这个请求时自动带上了你之前登录留下的 Cookie。银行服务器一看Cookie 有效请求是从用户浏览器发出来的也没校验请求到底是不是用户主动操作的于是就把钱转出去了。整个过程里攻击者骗的是服务器服务器以为请求是用户本人在当前网站页面上发起的实际上用户只是打开了另一个网站请求是被第三方页面“借”浏览器之手发出去的。这两个场景一个在讲“恶意代码如何进入页面并执行”一个在讲“伪造请求如何被服务器接受”。这就是最初级的本质区别XSS 是代码注入问题CSRF 是请求伪造问题。1.2 受害者视角不同修复思路自然不同从受害者角度看XSS 的受害者是“看到这个页面的用户”可能是网站管理员也可能是普通访客。CSRF 的受害者也是“已登录的用户”但被攻击的前提是用户恰好处于登录态且目标网站没做来源校验。可别小看这个视角差异。它直接决定了漏洞修复的归属XSS 修不好是“输出净化”和“前端渲染规范”的问题由内容和前端代码负责CSRF 修不好是“请求校验”的问题主要靠后端接口加 token、校验来源。你如果看到一份漏洞报告只写了“存在跨站脚本漏洞”先别急着在接口层加 token那是南辕北辙。我见过一个真实案例某系统被扫出跨站脚本漏洞开发同学说“我们已经在接口层做了鉴权没登录拿不到数据”结果复查时问题依旧。因为 XSS 发生在浏览器端渲染阶段跟接口鉴权没关系攻击者 POC 用的是未登录也能访问的搜索页面参数被直接反射到 HTML 里执行了。后来加了输出编码问题才真正解决。这就是不理解漏洞本质导致的“假修复”。2. 攻击机制逐层拆解脚本注入与请求伪造2.1 XSS 的三种形态别再只盯着反射型XSS 不是一个单一场景一般分成反射型、存储型和 DOM 型三种。很多文章把反射型当成 XSS 的代表来讲但实际修复和排查时三种形态的差异非常关键。反射型 XSS是最容易被安全扫描器发现的。用户提交的参数被服务端接收后没有经过任何处理就拼到返回的 HTML 里。比如一个搜索页面输入框里的关键词会显示在页面上“您搜索的是xxx”。如果这个 xxx 没有被转义攻击者构造一个特殊链接诱使受害者点击脚本就在受害者的浏览器里执行了。特征很明显恶意 payload 在 URL 里服务端负责“反射”到页面。存储型 XSS的危害更大。恶意脚本先被提交到服务器数据库里之后所有访问该页面的用户都会触发。评论区、留言板、用户昵称、资料编辑这些都是重灾区。一旦某个管理员账号查看了包含恶意脚本的页面攻击者就可能拿到管理员的会话凭证直接接管后台。存储型 XSS 更强调对“数据入口到展示出口”整条链路的审查单靠给 URL 参数做过滤不一定管用。DOM 型 XSS跟前两者有个本质差异它不一定经过服务端。前端代码直接用innerHTML、document.write、location.hash这类接口把 URL 参数或者本地存储里的内容当作 HTML 解析了。攻击链完全发生在浏览器端服务端日志里甚至看不到恶意请求。这意味着你加再多服务端过滤输出编码都拦不住 DOM 型 XSS得从前端代码层面去改。很多团队第一次遇到 DOM 型 XSS 时都很困惑因为扫描器明明报了 URL但看服务端日志什么都没有源头在 JS 代码里。2.2 CSRF 的攻击链路与核心条件CSRF 要成功一般得满足三个条件用户已经登录了目标站点且浏览器持有有效的会话凭证Cookie目标站点的敏感操作接口没有校验请求来源或者只依赖 Cookie 判断身份用户访问了攻击者精心构造的第三方页面。攻击者页面里的“武器”可以非常简单。如果是 GET 请求就能完成的操作一个图片标签就够了如果是 POST 请求攻击者可以写一个隐藏的表单页面一加载就自动提交。整个过程用户完全无感知浏览器也不觉得有什么问题——它只是忠实地“替你”带上 Cookie 发了一个请求。这里绕不开的一个核心机制是Cookie 的自动携带。浏览器为了让你在不同页面之间访问时保持登录态只要请求发往的目标域名匹配就会自动带上该域名下的 Cookie而这个行为不分请求是从哪个页面发起的。服务器如果只靠 Cookie 判断“请求者是谁”却不在乎“这个请求是不是用户在当前站点页面上主动触发的”就给了 CSRF 钻空子的机会。2.3 危害半径的差异为什么 XSS 常常更“致命”CSRF 能做的操作一般局限在目标站点的功能范围内改个密码、转笔账、发个帖子。它很难“读取”数据因为攻击者发起的请求最终响应落在受害者浏览器里攻击者看不到返回内容除非配合其他接口把数据外带。XSS 则不一样。脚本在受害者的页面上下文里执行等于攻击者拿到了一个“页面内部的视角”。他可以读取 Cookie、读取页面上的敏感数据、嗅探用户键盘输入、修改页面内容诱导用户再输入密码、甚至调用后端 API 以用户身份做任意操作。所以安全圈常说XSS 常常是“夺舍”CSRF 是“借刀”。有经验的测试人员一旦发现存储型 XSS通常会尝试往会话接管方向利用而 CSRF 一般被归为逻辑漏洞危害大小完全取决于受影响接口的敏感程度。如果那个接口只是改个用户昵称危害就低如果接口能改支付密码那危害就非常高了。3. 五个维度看清 XSS 与 CSRF 的本质区别这个部分是全文核心。很多对比文章只是把两个漏洞的定义各写一遍看得人更晕。我换一种做法用一张五个维度的对照表再逐个展开解释。对比维度XSS跨站脚本CSRF跨站请求伪造攻击对象浏览器端针对访问页面的用户服务端接口针对已登录用户的会话核心成因未对用户输入做过滤和输出编码导致恶意脚本执行请求未校验来源仅凭 Cookie 等凭证判断身份触发方式用户点击恶意链接、访问被植入脚本的页面用户在登录态下访问了第三方恶意页面攻击意图窃取数据、篡改页面、冒充用户操作冒充用户执行特定操作通常不直接读取数据修复重心输出编码、CSP、输入白名单、前端安全渲染CSRF Token、SameSite Cookie、校验 Origin/Referer3.1 从信任模型上看谁“背叛”了谁Web 安全里很多漏洞本质都是信任模型出了问题。XSS 的问题在于服务端信任了用户输入前端渲染时又没有重新建立“内容与代码”的边界。你把用户提交的字符串当成页面的一部分输出等于把一个陌生人放进你家客厅还给了他话筒。凡是没有白名单校验的富文本、文章、评论展示都容易踩这个坑。CSRF 的问题在于服务器信任了“浏览器自动携带的凭证”就等于用户本人的意志。浏览器出于便利性设计会主动把 Cookie 带在跨站请求上服务器却把这种“来自浏览器”的请求等同于“来自用户操作”这就形成了一个信任缺口攻击者只是利用浏览器作为中间人请服务器开了门。用一个生活类比可能更好记XSS 等于有人往你办公室的公告栏里贴了一张带病毒的二维码每个路过的人都会去扫CSRF 等于有人捡了你的工牌复印件到门禁那里一晃门禁系统只认工牌不认人就放他进去了。一个是内容被污染一个是身份被冒用防线位置完全不同。3.2 触发条件一个需要“代码执行”一个只需“请求发出”判断一个漏洞到底是 XSS 还是 CSRF最快的办法是看攻击成功的最小条件是什么。XSS 要成功必须有“脚本在目标页面上下文里执行”这个环节。所以攻击者才费尽心思去 bypass 过滤、寻找输出点。如果你看到 POC 里出现了script、onerror、javascript:这些关键词那基本就是 XSS 方向。CSRF 要成功不需要脚本执行。哪怕攻击者页面禁用 JavaScript照样可以用一个纯 HTML 表单发请求。你看到的 POC 往往是一个独立的 HTML 页面或者一段自动提交的表单代码。它关注的是“这个请求发出去以后服务器认不认”。这个区分在实战里特别有用。漏洞报告里如果给了 URL 和一个请求包你看请求包里是否带了一个不可预测的 token如果没有 token 或者 token 是固定的并且修一下请求方法就能复现那大概率是 CSRF而不是 XSS。3.3 拿到的“权限”也不一样从攻击链路末端来看XSS 一旦执行成功攻击者获得的是“受害者浏览器里的页面权限”。这个权限可以实时变化——用户在页面上输入什么脚本都能看到用户跳转到站内哪个子页面脚本也能跟着注入。攻击者甚至可以写一个键盘记录器长期蹲守。CSRF 则更像“一次性命令注入”。攻击者预先构造好一个请求服务器执行完就结束了攻击者拿不到执行结果的回显。例如通过 CSRF 修改了用户邮箱攻击者确实能达成后续“找回密码”的目的但他并不能看到“修改成功”页面里返回的用户信息。深入理解这一点你在做风险评估时就能分得清轻重一个存储型 XSS 如果打到管理后台基本等于后台沦陷而一个改昵称的 CSRF只要不影响核心业务一般排在低优先级慢慢修。漏洞有危重之分先把高危的处置掉再回头处理低危的才不会把自己搞得草木皆兵。4. 实操视角用 Pikachu 靶场亲手复现一次4.1 Pikachu 靶场里的 XSS 模块怎么玩才有效理论讲多了容易飘我建议你自己动手在本地搭一套 Pikachu 靶场动手之前务必确认它运行在隔离环境里。这是一套专门用来练习 Web 漏洞的 PHP 靶场里面把 XSS 和 CSRF 都单独拆了模块特别适合做对比实验。进到 XSS 模块后你可以先做反射型。页面上有个输入框随便输入一段字符串提交观察 URL 参数和页面响应。接着换个思路输入一段scriptalert(document.domain)/script看看浏览器是否弹窗。弹窗说明脚本已经在你自己的浏览器上下文里执行了。再打开开发者工具切到 Network 面板看那个请求的响应体——你会看到服务端确实把这段脚本原样返回了这叫反射型服务端参与了“反射”责任在后端输出。再去 DOM 型 XSS 模块。同样是输入框但这次你提交的内容会跑到 URL 的 hash 部分#后面的内容。你刷新页面脚本依然执行但是看 Network 面板可能根本没有把这个内容传到服务端的请求。这就是 DOM 型的关键特征整个渲染过程由前端 JS 直接操作 DOM 完成后端根本不知道你输入过什么。做完这两个实验你就能直观理解为什么反射型 XSS 可以在服务端做输出编码修复而 DOM 型 XSS 必须在 JS 代码层面把innerHTML换成textContent或者使用安全的 DOM 操作 API。同样叫 XSS修复点完全不同。4.2 复现 GET 型 CSRF一个 URL 就能把状态改了Pikachu 的 CSRF 模块分 GET 和 POST 两种。我的建议是先玩 GET 型。你登录靶场以后找到“修改个人信息”这类功能里面一般是个表单提交方式是 GET。你修改一下昵称提交然后看浏览器地址栏你会发现整个修改操作的关键参数都在 URL 里躺着。这时你可以复制这个完整 URL关掉表单页面在新标签页里再访问一次。结果如何昵称又变回攻击者想要的值了。这个“复制 URL 到新标签页访问就能生效”的过程本质上就是一次 CSRF 攻击的最小演示——因为浏览器发起 GET 请求时自动带上了该域名下的 Cookie服务器只看到“请求来自一个已登录用户”就直接执行了。你自己手动造不出那个带着所有参数的恶意 URL但如果有攻击者把它藏在第三方页面的图片标签里效果一模一样。POST 型 CSRF 的复现稍微复杂一点需要用 Burp Suite 抓包把 POST 请求转成一个自动提交表单的 HTML 页面再放到本地打开测试。你会发现只要服务器没有校验 token也没有校验 Referer这个自动提交的页面同样能完成操作。区别仅仅在于 GET 型的 POC 更简单所以外部漏洞报告里 CSRF 的案例常以 GET 型为主。4.3 为什么 CSRF 的复现往往看起来“不够酷炫”很多初学者攻击完 CSRF 后总觉得不过瘾没有弹窗没有数据外带只是默默把数据改了甚至看不出波澜。这恰恰体现了 CSRF 的特点它不追求“炫技”它追求“动作完成”。理解了这一点你再去看漏洞报告里的 CSRF 就会更冷静。报告不会给你看炫酷的脚本它只会给你贴一段请求包注明“该请求未包含 CSRF Token”。你要做的不是复现一个视觉冲击力强的攻击而是去确认这个请求有没有不可预测的参数有没有校验来源如果都没有漏洞就算数。我自己排查时会额外看一眼这个请求是不是“幂等”的。所谓幂等就是同一个请求重复执行效果不会叠加。GET 型 CSRF 复现后如果只是改了同名数据影响还好评估但如果一个接口反复调用会扣钱、发消息、生成订单那 CSRF 的危害会被放大很多倍。测试报告里写“高危”多半也是因为这类副作用接口存在。5. 修复方案完全不同从 XSS 三步修复到 CSRF 403 排查5.1 反射型 XSS 的“三步修复”怎么落地经常有外部安全扫描报告把反射型 XSS 报得很详细有些报告甚至会给出修复建议。我认可一个说法反射型 XSS 的常规修复可以总结成三步这也是很多企业安全团队验收时的统一口径。第一步定位。找到用户输入在哪里被接收、又在哪里被输出。用开发者工具看响应或者直接搜代码里的echo、print、模板渲染语句找出所有把参数拼进 HTML 的地方。这一步不做好后面都是盲修。第二步判断输出上下文。同一个参数渲染到 HTML 标签里、属性里、JavaScript 字符串里修复方式都不一样。在 HTML 正文里要做 HTML 实体编码在href属性里要做属性编码并禁止危险协议在script字符串里要做 JS 编码。一个常见的坑是只做 HTML 实体编码却忽略了属性或 JS 上下文扫描器复查仍能绕过。第三步输入侧加固。虽然输出编码是主防线但输入侧白名单也不可少。业务上如果允许的字符集有限比如用户名只允许中英文和数字就在输入侧直接拦截。这能减少后续的编码负担但不要把它当成唯一防线——因为输入过滤很容易被各种编码和绕过手法击穿输出编码才是兜底的那道闸。提示报告里如果写的是“存储型 XSS”请把第三步的范围扩大。凡是会存库再展示的字段都要按“存储-渲染”两条链路重新排查比如历史存量数据不可能全改一遍输入这时输出编码的重要性更加突出。5.2 为什么 CSRF 修复首选 Token 而不是 RefererCSRF 修复最主流的方案是加 CSRF Token也就是在表单或请求头里放一个随机的、与用户会话绑定的不可预测值。服务器收到请求后先校验这个 Token不对就拒绝。原理很简单攻击者的第三方页面上无法提前知道当前用户会话里 Token 的值所以构造不出合法请求。也有团队想走捷径用校验 Referer 或 Origin 的方式。这两个头确实能标识请求来源但 Referer 有一个很尴尬的问题为了隐私部分浏览器或浏览器插件会裁剪它部分老系统部署在 HTTPS 页面里Referer 可能为空导致正常用户都被误伤。相对而言Origin 头在现代浏览器里更可靠但仍然不是 100% 稳定。所以行业共识是Referer/Origin 校验只能作为辅助Token 才是主角。Token 实现时有个细节经常被忽略Token 一定要绑定会话还要随机生成。有些系统把 Token 写死在前端代码里等于没有有些系统 Token 固定一段时间不换被泄露后长期有效还有一些把 Token 放 URL 里通过 Referer 泄露出去了。这几种都是“看似修了实则没修”。5.3 排查 403 CSRF接口报错别急着骂前端如果你做的是前后端分离项目排查 CSRF 问题时最常遇到的报错就是“接口返回 403”错误信息里写着CSRF之类。这种报错大多数不是漏洞而是防护组件生效了——Spring Security、Django、Laravel 这些框架默认开启了 CSRF 校验。遇到 403 时先按这个顺序排查请求里有没有带上页面渲染时生成的 Token前端如果是自己用fetch发请求经常漏掉从 Cookie 或 DOM 里取 Token 这一步。Token 有没有过期或错位比如用户停留在页面上很久才提交Token 已过期或者页面开了多个标签页不同标签页的 Token 互相覆盖。前后端域名不一致时Cookie 是否能正常写入如果 Token 放在 Cookie 里跨域请求默认不带也会 403。后端有没有把请求路径排除在校验范围之外有些接口因为历史原因没纳入 CSRF 保护这时就要判断这个接口是不是敏感操作如果不敏感排除逻辑可以接受如果敏感得把保护补上。有一次我带项目排查一个诡异的“用户频繁反馈保存失败”最后发现是新来的前端在封装请求工具时把 Token 字段写死成了一个固定测试值联调时能通一上生产就 403。这种问题不在漏洞层面而在联调规范。后来我们把 Token 获取统一封装线上就安静了。6. 常见误区与实战排查技巧速查6.1 把 XSS 和 CSRF 混为一谈时的典型误判这里整理一份我实际评审中见过的高频误区每一条都对应过真实项目返工。误区一认为 HttpOnly 能防住 XSS。HttpOnly 只是让 JavaScript 读不到 Cookie能降低 XSS 后的会话劫持风险但 XSS 脚本仍然可以改页面、发请求、录键盘。只开 HttpOnly 不修 XSS等于只把门锁换了但窗户还开着。误区二认为 CSRF Token 放在 localStorage 里就安全。如果页面存在 XSS脚本可以直接读取 localStorage 里的 Token再伪造请求。Token 防的是“第三方伪造”防不了“同页面恶意脚本”。所以 CSRF Token 要配合 XSS 修复一起做单靠 Token 扛不住 XSS 的冲击。误区三CSSRF 只存在于 GET 请求。有人说“我们把操作全改成 POST 就安全了”这是误解。POST 请求同样可以被自动提交表单触发只是构造成本比 GET 高一点。真正关键的不是请求方法而是有没有不可预测的校验参数。误区四扫描器报了 XSS就一定是反射型的判断。有些扫描器会把 DOM 型 XSS 的告警也标成“反射型”因为它的检测入口确实是一个带参数的 URL。拿到报告以后一定要手动打开页面看看恶意参数到底是在服务端响应里出现还是只在前端 JS 里流转。修错层级的代价很大必须人工复核。6.2 现场排查时我的一句话判断法最后分享一个很土但很实用的判断方法。拿到一个安全问题先问自己一句如果用户什么都不点、什么都不输入只是打开了一个页面问题会发生吗如果答案是“会”那基本是 CSRF——这个请求在你打开页面的一瞬间就发出去了浏览器自动带上了 Cookie。你排查的方向是请求校验和 Token。如果答案是“不会用户得点一个链接或者得在某处提交一段内容”那大概率是 XSS 相关。再顺着“链接里的参数会渲染在页面哪个位置”、“提交的内容会展示在哪个页面”往下查很快能定位到反射型还是存储型。这个方法不能替代专业测试但它能在海量漏洞报告里帮你快速分诊。我靠这个“一句话判断法”过滤掉过不少误报和低质报告也帮开发同学减少了很多无效返工。6.3 你在修复时应当建立的最小验收清单如果团队没有专门的安全测试人员建议每次修复完 XSS 或 CSRF 后对照这份清单自查一遍避免返回去又被测试打回XSS 修完后按修复前 POC 原样测一遍确认不再执行再试几个常见变形比如大小写混合、HTML 实体编码、事件属性触发onerror、onclick等确保编码逻辑没有漏掉上下文。如果修复依赖输入侧过滤请确认同样场景下存储型 XSS 的展示路径也被覆盖特别注意“存量数据”在改动前的历史记录仅靠输入过滤管不到已经入库的脏数据。CSRF 修完后用浏览器开发者工具把请求里的 Token 删掉或篡改看服务端是否拒绝再把 Token 改成另一个会话的 Token确认校验绑定的是“当前会话”而不是“任意登录状态”。确认前端在 Token 过期后能给用户明确提示不要只弹一个晦涩的 403 JSON 错误否则上线后会被用户骂。涉及核心业务操作额外检查一次“是否需要二次校验”比如改密、转账这类敏感场景即使加了 Token加上短信验证码或输入原密码也能极大降低“已登录会话被借刀”的风险。以上这些条目与其说是安全验收标准不如说是我踩坑踩出来的“术后回复清单”。修复漏洞不是把 POC 压下去就行而是要确保同类问题不会在代码重构、功能扩展时再次冒出来。我在实际项目中还有一个感受XSS 和 CSRF 这两个词频繁一起出现并不只是因为它们都带“跨站”俩字。很多团队的安全建设走到一定阶段都会同时遇到这两类问题。真正把它们分开理解之后你会发现后续再看 SQL 注入、SSRF、越权漏洞时会更容易抓住各自的信任模型。安全漏洞的底层逻辑是相通的想清楚系统信任了什么、没校验什么攻击路径自然就清晰了。最后再分享一个我给自己团队定的“土规矩”所有对外提交的接口默认都要做三件事——输出编码按上下文做干净、请求校验加不可预测 Token、Cookie 能开 HttpOnly 和 SameSite 就开起来。这三件事立住一半以上的 XSS 和 CSRF 问题根本走不到线上。
返回列表