
《Web 安全专题 06|CSRF 防护的重点在于状态改变》
栏目序号:第 6 期/篇
导语:CSRF(跨站请求伪造)的防护目标并不是所有请求一律加令牌,而是要覆盖“会改变服务器状态”的操作。本文围绕表单提交、媒体上传和后台审核三个常见场景,讨论如何判断哪些操作必须检查 CSRF、令牌应当覆盖的边界,以及在工程实践中容易被忽视的细节和误区。
何为“状态改变”,为什么要聚焦它
在讨论令牌覆盖范围之前,先把“状态改变”下定义:任何在服务器端引入、修改或删除持久化数据,或触发不可逆副作用(例如发邮件、推送通知、启动异步任务)的请求,都属于状态改变。相对地,单纯返回资源、提供查询或渲染视图的 GET 请求通常被视为“安全”的读取请求。将防护聚焦到状态改变可以避免浪费资源于无需保护的接口,同时降低误报。
判断边界时要注意两点:一是副作用的隐蔽性——例如访问某个 URL 可能触发统计计数或签名过程,这些都算副作用;二是可见性——有些操作(如上传成功后异步触发垃圾扫描或转码)其主请求看似只接收文件,但实际上会启动后续改动或资源发布,因此也应纳入保护。
表单提交:传统场景的判定与实践
表单提交是 CSRF 最早和最直观的攻击面。凡是 POST/PUT/PATCH/DELETE 等非安全方法提交用于创建、修改或删除数据的表单,都应使用 CSRF 令牌。实践要点包括:
- 在服务端为登录会话分配与会话绑定的 CSRF 令牌,令牌应足够随机并在服务端验证。
- 将令牌嵌入到每个需要保护的 HTML 表单中(隐藏字段),并在接收端验证与会话对应性。
- 对于通过 AJAX 提交的表单,可以将令牌放到响应中的元信息(例如 JSON 或 meta 标签),并由前端在请求头(例如 X-CSRF-Token)中携带。
- 对于 API 风格的后端,如果客户端使用 Authorization: Bearer 之类的头部认证,则同源浏览器的自动凭证(cookies)问题较少,但仍需考虑跨站脚本和第三方托管页面的风险,必要时仍应校验额外的防护(例如要求非浏览器客户端通过令牌或签名)。
注意:不要仅通过判断 Content-Type 就跳过校验。浏览器对 multipart/form-data 的表单提交仍会自动带上 cookie,因此 multipart 的文件表单同样容易被 CSRF 利用,除非另有强认证措施。
媒体上传:不仅仅是文件内容的保护
媒体上传场景常见于用户图片、视频或文档的提交,这类接口遇到的实际问题更复杂。上传流程通常包括上传入口、分块/断点续传、转码或存储回调。对于 CSRF 令牌的覆盖要点:
- 上传入口(接受文件的 HTTP endpoint)如果由浏览器发起并会绑定到用户会话,必须验证 CSRF 令牌。攻击者可以诱导浏览器提交带有 cookie 的 multipart 请求,上载任意文件到受害用户的账户空间。
- 分块上传接口(chunk)若每个分块独立请求,则每个分块亦可能需要验证令牌,或者使用一次性上传会话 ID(由受保护的单次表单请求创建)来替代对每个请求都校验令牌的成本。
- 上传完成后触发的异步任务(转码、发布到 CDN、通知其他用户)属于后续状态改变,必须确保这些任务只能由合法上传流程启动,例如在上传完成回调里校验上传会话 ID 与令牌绑定关系。
- 不要将文件的存在或名称视为安全边界;上传接口若允许覆盖资源或写入可执行路径,会带来更高风险,需额外检测文件类型、大小并在必要时隔离存储位置。
简而言之,媒体上传的所有入口点——包括分片、合并、回调——都要评估是否属于“会改变状态”的操作,并据此决定是否必须有 CSRF 验证或等价的会话绑定机制。
后台审核与批量操作:高价值动作的额外保障
后台审核、管理员批准、批量删除或导出这些操作通常影响范围大、回溯成本高,因此对 CSRF 的要求应更严格。对这些场景的建议:
- 所有管理面板的“写”操作必须强制 CSRF 验证,并建议在关键操作前增加二次确认或要求重新认证(例如输入密码、2FA),因为令牌本身只证明请求来自会话,而不能保证当前操作是由合法管理员在其意愿下发起。
- 批量操作接口(例如一次性删除成千条记录)应限制每个请求改变的范围,或要求额外的防护令牌来确认操作意图。如果业务允许,拆成较小的批次并记录审计日志可以降低风险。
- 审核类操作往往伴随通知或外部回调(例如给用户发送邮件),应在执行这些副作用前校验令牌并记录操作来源与上下文,以便事后追踪。
此外,后台接口不应盲目信任 Referer/Origin 头部为唯一防护手段。作为补充手段,Origin/Referer 检查能抵御一部分跨站伪造,但在某些网络拓扑或代理环境中可能不可靠,不能替代 CSRF 令牌验证。
防护的重点在于状态改变:只要请求能改变服务器上与用户或系统有关的持久状态,就应该被认为需要保护。
如何在工程中落地:判例与步骤
给出一套可执行的清单,便于在现有项目中审视并部署 CSRF 覆盖:
- 枚举接口:列出所有 HTTP 接口,标注方法、是否跨站暴露、是否依赖 cookie/session、以及是否会产生持久化改动或副作用。
- 分类判定:把接口分为“只读”、“可能副作用”、和“明确写操作”。对“明确写操作”默认强制 CSRF;对“可能副作用”进行详细审查。
- 统一中间件:在框架层(例如 Flask 或其他 Web 框架)使用中间件或装饰器统一校验 CSRF 令牌,避免在业务逻辑层分散实现导致遗漏。
- 设计令牌策略:选择令牌与会话绑定还是双提交 cookie;在浏览器端通过安全的方式注入令牌,并在 AJAX 请求里通过自定义头部传送。令牌应可重置但不应频繁无谓刷新以免影响 UX。
- 审计与测试:在自动化测试中加入 CSRF 绕过测试、对分块上传、后台批量接口进行模糊测试,并在生产环境中记录未通过验证的请求以便分析攻击模式。
- 兼容性与补充:为移动应用或 API 客户端设计替代认证方式(如 Authorization 头部的签名或 JWT),并明确这些接口的 CSRF 风险模型。浏览器端可辅以 SameSite Cookie、Origin 检查等手段作为防护组合,但不要把它们作为唯一手段。
在具体框架实现上,可以参考现有文档的建议与实践,例如框架内置的 CSRF 支持和认证清单。部署时应结合框架能力与业务场景调整策略。
常见误区与风险点
- 误区:GET 请求绝对安全。实际上,设计不当的 GET 请求可能带来副作用(例如利用 GET 触发统计增加或临时授权),这类接口应修正为 POST 并受 CSRF 保护。
- 误区:只要有 SameSite=Lax 就万无一失。SameSite 在某些旧版浏览器中不一致,且 Lax 允许部分跨站导航请求携带 cookie,不应单独依赖。
- 风险点:第三方脚本与内嵌小部件。页面加载的第三方脚本如果被滥用,可能发起带 cookie 的请求。减少第三方信任范围、使用 CSP、并确保关键写操作有 CSRF 验证可以降低风险。
- 误区:Referer/Origin 校验可以取代 CSRF。它们能提升安全性但并不总可靠(有些环境会屏蔽或修改头),且不能证明用户意图,因此应作为补充而非替代。
在工程判断时,优先从“如果攻击者能在用户浏览器里发起该请求,是否会造成危险”来界定是否需要 CSRF 保护。
延伸阅读
- Flask Documentation: https://flask.palletsprojects.com/en/stable/
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
Comments · 0
暂无评论。