
做Web开发的这几年我见过太多被XSS和CSRF搞到焦头烂额的团队。这两种攻击不像SQL注入那样直接报错明显它们更像是潜伏在业务逻辑里的暗雷平时查不出问题一旦爆了就是用户数据泄露、账号被盗、资金损失这类大事故。我自己的项目也在这上面交过学费所以这篇想把XSS与CSRF的对抗策略一次讲透从攻击原理到防御落地再到检测与复盘全部用实际踩坑经验说话。先说清楚这俩的定位XSS跨站脚本攻击利用的是“用户输入不可信”攻击者把脚本注入到页面里在别人浏览器中执行CSRF跨站请求伪造利用的是“浏览器自动携带身份凭证”的特性在用户不知情时伪造请求。两者的攻击路径、防御思路完全不同但经常被一起提及因为它们都发生在HTTP请求与浏览器渲染这条链路上而且很多团队真正遇到问题时往往发现单一防护手段撑不住。这篇文章适合前后端开发、安全工程师、技术负责人阅读。我会尽量把原理讲得浅显把落地策略讲得具体从编码函数怎么设计、Token怎么存、CSP怎么配到常见绕过手法怎么防全部覆盖。你可以把它当成一份可参考的加固手册。1. 先摸清对手XSS与CSRF的攻击链路拆解1.1 XSS的本质浏览器把“数据”当成了“代码”XSS的核心问题是程序在输出用户可控数据时没有区分“数据”和“代码”的边界。正常来说用户输入的内容应该只是被当作纯文本展示但如果它被插入到HTML、JavaScript、URL等上下文里且未做处理浏览器就会把它解析为可执行的代码。按触发类型XSS通常分三类反射型XSS恶意脚本藏在URL参数里服务端或前端拼接后直接返回给当前用户。比如搜索页把关键词直接渲染到页面未编码就执行。这种攻击需要诱骗用户点击恶意链接一般通过钓鱼消息传播。存储型XSS恶意脚本被提交到服务器并持久化存储其他用户访问该页面时就会触发。论坛签名、评论、昵称、富文本内容都是重灾区。DOM型XSS攻击载荷不经过服务端而是由前端JavaScript读取URL、location.hash、postMessage等数据后直接写入了innerHTML、document.write或eval等危险API。我在做某个内容社区项目时就曾复现过一个典型的DOM型漏洞前端从一个活动页的URL参数中读取campaign名称然后拼接到页面的HTML里展示活动横幅。攻击者只要构造一个参数值带上script标签就能在用户浏览器里执行任意脚本。讽刺的是服务端接口完全没参与防护层根本看不出问题。1.2 CSRF的本质浏览器“代你请求”的信任滥用CSRF的关键在于Cookie等身份凭证会在请求时被浏览器自动携带且同源策略只限制“读取响应”并不限制“发送请求”。所以攻击者可以构造一个跨站请求让浏览器带着用户对目标站点的Cookie去请求目标接口。一个典型的CSRF攻击场景是这样的用户登录了某银行站点登录状态以Cookie形式保存在浏览器里。然后用户在另一个标签页打开了攻击者准备的恶意页面这个页面里有一张隐藏的图片或一段自动提交的form向银行站点的“转账”接口发起POST请求。浏览器自动带上Cookie服务器一看Cookie有效认为是合法用户操作转账就执行了。举一个更具体的例子某个后台管理系统的“修改管理员邮箱”接口是POST /admin/email参数只有new_email。没有Token没校验Referer也没用JSON格式。攻击者只需在论坛发一篇帖子里面放一个自动提交的form就能把管理员邮箱改掉接着通过“找回密码”接管整个后台。这类案例我在渗透测试中遇到不止一次。CSRF能成的前提有三个目标接口使用Cookie等自动携带的凭证、接口未校验请求来源、接口状态变更方式是POST或GET且参数可预测。抓CSRF防护本质就是打破这三个前提中的至少一个。1.3 为什么XSS和CSRF经常被放在一起讨论从攻击链上看XSS是“在受害者的浏览器里执行任意脚本”而CSRF是“替受害者发一个请求”。一个研究深的人会发现利用XSS可以完全绕过CSRF防御——因为攻击者已经在目标站点的同源上下文里执行脚本Token、Cookie、SameSite这些措施全都可以被脚本直接访问。反过来说CSRF有时候也被用来配合XSS打组合拳比如先通过CSRF修改用户资料再触发存储型XSS。所以真正硬核的防御从来不是“防了XSS就不管CSRF”而是一个纵深防御体系XSS防护解决“脚本注入与执行”的问题CSRF防护解决“伪造请求”的问题两者互为补充并最终由浏览器安全机制兜底。2. XSS防御从输出编码到CSP的纵深防线2.1 输出编码是第一道闸门但必须分上下文很多人以为做输出编码就是“把过滤掉”这是天大的误解。XSS的上下文多种多样编码规则完全不同HTML标签内容上下文插入到内容区需要做HTML实体编码把、、、等转为、、、。HTML属性上下文插入到title、src等属性值里除了HTML实体编码还要特别处理引号和空格否则攻击者可以用 onerror... 逃逸出属性。JavaScript上下文插入到