一、一张表看清区别
| XSS | CSRF | |
|---|---|---|
| 攻击者目标 | 在受害者浏览器执行脚本 | 借受害者的登录态发请求 |
| 是否需要用户点击 | 存储型不需要,反射型需要 | 通常需要(诱导打开页面/点链接) |
| 能否读取响应 | 能(同源脚本) | 不能(跨站读不到响应) |
| 核心防护 | 输出转义 + CSP | CSRF Token + SameSite |
| HttpOnly 有用吗 | 有用(保护 Cookie 不被读) | 无用(浏览器照样自动带 Cookie) |
关键差异:XSS 一旦成功,等于 attacker 拿到了页面控制权,后续所有防护都可能被绕过;CSRF 只是「冒用身份发一次请求」,读不到返回内容。
二、XSS 的三种形态
- 存储型:恶意内容存进数据库(评论、昵称、商品描述),所有访问者中招。危害最大。
- 反射型:payload 在 URL 里,服务端原样回显到页面(搜索词、错误提示)。需要诱导点击。
- DOM 型:服务端没参与,前端 JS 把
location.hash、innerHTML直接写进页面。
注入点远不止 <script>:<img onerror>、<svg onload>、javascript: 协议、CSS 的 expression、甚至 <iframe srcdoc>。所以「过滤 <script> 关键字」这种黑名单一定会被绕过。
三、XSS 防护:输出编码 + CSP
- 按上下文转义:HTML 文本、属性值、JS 字符串、URL 参数各有规则(详见 HTML 实体编码)。
- 富文本用白名单:允许哪些标签、哪些属性,其余一律删。用成熟库(DOMPurify),不要自己写正则。
- CSP:
Content-Security-Policy限制脚本来源,即使被注入也无法执行外链脚本。script-src 'self'是最有价值的一条。 - HttpOnly Cookie:即使被注入,脚本也读不到会话 Cookie。
- 前端框架注意
innerHTML/dangerouslySetInnerHTML:默认转义是保护,绕过它就要自己负责。
四、CSRF 的利用条件
三个条件同时成立才能利用:
- 浏览器会自动带上凭证(Cookie、Basic Auth)。
- 参数可预测(没有随机 token)。
- 服务端不校验请求来源。
典型场景:用户登录了银行站点,访问攻击者页面,页面里一个隐藏表单自动 POST 到 bank.com/transfer,浏览器带上 Cookie,转账成功。
注意:只靠 POST 不够。攻击者可以用自动提交的表单发起 POST;fetch 跨域虽然读不到响应,但请求照样发出去(简单请求)。所以「改成 POST」不是防护手段。
五、CSRF 防护
- CSRF Token:服务端下发随机 token,放在表单隐藏域或自定义头里,校验时才放行。攻击者跨站读不到 token,也设不了自定义头。
- SameSite Cookie:
SameSite=Lax能挡住绝大多数跨站 POST;Strict更严但会影响从外部链接进来的体验。 - 校验 Origin / Referer:简单有效,但要注意有些隐私设置会去掉 Referer。
- 关键操作二次确认:改密码、转账要求重新输密码或验证码。
- 不要用 GET 做写操作:
GET /delete?id=1一张<img src>就能触发。
六、两者叠加的情况
如果站点有 XSS,CSRF Token 也可能被脚本读走——因为脚本运行在同源下。所以 XSS 是更根本的问题,修好 XSS 才能让其它防护生效。
七、自检清单
- 所有用户输入在输出时是否按上下文转义?
- 富文本是否走白名单?
- Cookie 是否带
HttpOnly、Secure、SameSite? - 写操作是否校验 CSRF Token 或 Origin?
- 是否配了 CSP 且没有
unsafe-inline? - 有没有把用户输入拼进
innerHTML、eval、document.write?
需要验证转义效果时,用 HTML 实体编解码 看输入输出;URL 参数场景用 URL 编码。
小结
XSS 是「执行我的脚本」,靠转义 + CSP + 白名单;CSRF 是「借你的 Cookie 发请求」,靠 Token + SameSite + Origin 校验。HttpOnly 防 XSS 偷 Cookie,但完全挡不住 CSRF。