ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Go Web 安全实战:CSRF 跨站请求伪造的原理与预防(build-web-application-with-golang 第九章)

2026/10/5 8:25:17 拓冰建站 浏览量
Go Web 安全实战:CSRF 跨站请求伪造的原理与预防(build-web-application-with-golang 第九章) 文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载本篇围绕《build-web-application-with-golang》日文版第九章 9.1 节 「CSRF 攻撃の予防」 展开系统讲解 CSRF跨站请求伪造攻击的定义、攻击原理与完整防御思路并结合仓库配套的 Go 源码实现ch.4.4 nonce 模块说明正确使用 GET/POST 非 GET 请求携带随机 token这套防护方案如何真正落地。读完本文你将理解 CSRF 为什么是沉睡的巨人并能在自己的 Go Web 应用中独立实现可复用的 token 防伪机制。CSRF 是什么借你的身份发它的请求CSRF 全称 Cross-site request forgery中文通常译为跨站请求伪造又称 one click attack单击攻击或 session riding会话劫持缩写为 CSRF/XSRF。CSRF 能做什么可以这样简单理解攻击者盗用你的登录凭据以你的身份向 Web 应用发送任意请求。攻击者往往只需要一点社会工程学手段——比如通过 QQ 等聊天软件发出一条链接甚至用短链接伪装用户难以辨别——就能诱导已登录的用户执行攻击者设定好的操作。例如用户登录网上银行查询余额后未退出登录此时点击了QQ 好友发来的链接用户银行账户中的资金就可能被转账到攻击者指定的账户。由此可见一旦遭受 CSRF 攻击终端用户的数据与操作指令将面临重大安全问题而如果被攻击的终端用户恰好拥有管理员账号那么 CSRF 攻击就可能危及整个 Web 应用程序。CSRF 的攻击原理两个前提与一张流程图原书用下面的流程图直观展示了 CSRF 攻击的思想从图中可以看出一次成功的 CSRF 攻击需要受害者完成两个步骤登录页面 A 并获得信任受害者在页面 A 完成登录浏览器在本地生成并保存 Cookie在未退出 A 的情况下访问危险页面 B受害者带着 A 的登录态去访问攻击者控制的页面 B页面 B 中潜伏的恶意代码便以受害者的身份向 A 发起请求。读者可能会想只要这两个条件任意一个不满足就不会遭受 CSRF 攻击。 理论上确实如此但现实中我们无法保证以下几种情况不会发生登录某个页面后无法保证用户不再新开一个标签页去访问其他页面——尤其现代浏览器普遍支持多标签页关闭浏览器后本地 Cookie 未必立即过期无法保证上一个会话已经真正结束图中所展示的攻击页面完全可能是某个本身存在其他安全漏洞、却被大量用户信任的常用页面。因此让用户登录后不再点击任何链接、不再进行任何其他操作几乎是不可能的用户随时都可能成为 CSRF 的受害者。原书进一步点出了 CSRF 攻击的根本原因Web 的隐式身份验证机制。Web 的身份验证机制只能保证这个请求来自某个用户的浏览器却无法保证这个请求是用户授权后发出的——这正是 CSRF 得以成立的本质缺口。CSRF 的防御从服务器端入手CSRF 的防御可以从服务器端与客户端两个方向着手其中从服务器端防御最为有效当前主流的 CSRF 防御方案也大多部署在服务器侧。服务器端预防 CSRF 的方法有多种思想大同小异主要围绕两个方面正确使用 GET、POST 与 Cookie在非 GET 请求中添加伪随机数。原则一正确使用 GET、POST 与 Cookie原书在前面的章节中介绍过 REST 风格的 Web 应用对于普通 Web 应用绝大多数请求以 GET、POST 为主辅以 Cookie 机制。在设计应用时应遵守以下约定GET 只用于查看、列举、展示等不会改变资源的操作POST 只用于下单、修改资源等会改变状态的场景。用 Go 语言如何限制资源的访问方式原书给出的例子mux.Get(/user/:uid, getuser) mux.Post(/user/:uid, modifyuser)这样处理后修改操作只允许 POST 方法当客户端用 GET 方法请求时会被拒绝从而挡住前面图中演示的GET 型 CSRF 攻击。但问题并没有完全解决——POST 同样可能被 CSRF 利用因此必须进入第二步。原则二非 GET 请求添加随机数三种方案对比对于非 GET 请求原书介绍了三种为其附加随机数的实现方案方案做法优点缺点1. 统一 Cookie Token为每个用户生成一个唯一的 cookie token所有表单都包含同一个伪随机数最简单攻击者理论上拿不到第三方 Cookie无法伪造表单数据用户 Cookie 可能被页面的 XSS 漏洞窃取只有在不存在 XSS的前提下才安全2. 每次请求使用 CAPTCHA每次请求都要求输入验证码防御效果完美需要反复输入验证码用户体验极差不适合实际运营3. 每个表单独立随机数不同表单各自携带不同的伪随机数兼顾安全与体验需要实现生成、下发、校验一整套逻辑原书指出方案 3 正是 4.4 节如何防止表单的多次提交中介绍过的方法可以直接复用到 CSRF 防御上。相关章节见 如何防止表单的多次提交4.4。Go 实战token 的生成、输出与验证下面是原书给出的完整实现分三步走。第一步生成随机 token这里用时间戳 盐值经 MD5 哈希得到h : md5.New() io.WriteString(h, strconv.FormatInt(crutime, 10)) io.WriteString(h, ganraomaxxxxxxxxx) token : fmt.Sprintf(%x, h.Sum(nil)) t, _ : template.ParseFiles(login.gtpl) t.Execute(w, token)其中crutime通常是time.Now().Unix()得到的时间戳ganraomaxxxxxxxxx是混入的固定盐值token 最终以十六进制字符串形式输出并渲染进模板。第二步在表单中输出 token隐藏字段随表单一起提交input typehidden nametoken value{{.}}第三步在服务器端验证 tokenr.ParseForm() token : r.Form.Get(token) if token ! { // token 的合法性验证 } else { // token 不存在时产生错误 }按照这套流程POST 请求基本可以认为是安全的。读者可能担心如果 token 的算法被破解了怎么办——原书认为这个字符串在理论上几乎不可能被强行破解原书给出的粗略估算为需要约 2 的 11 次方量级的计算时间该数字为原书表述实际强度取决于随机源与盐值的熵。需要说明的是上述简易实现中crutime只用了时间戳随机性有限因此在真实项目中应把 token 与用户会话绑定4.4 节也提到可以通过 session 保存 token 再比对session 的保存方式在第六章讲解并引入真正的随机源避免可预测性。仓库源码印证ch.4.4 的 nonce 模块如何落地这套方案原书 9.1 节直接引用了 4.4 节的方法仓库中恰好提供了 4.4 节对应的可运行实现可以作为上述方案的工程化升级版来印证与深化理解。token 生成从仅时间戳到时间戳 随机数 盐值nonce 模块 中createToken()在原书思路基础上增加了随机数func createToken() string { h : md5.New() now : time.Now().Unix() io.WriteString(h, strconv.FormatInt(now, 10)) io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)) return fmt.Sprintf(%x, h.Sum(nil)) }它把时间戳与rand.Int63()生成的随机数一起混入 MD5 哈希比原书示例仅时间戳 固定盐更难预测。模块还封装了完整的生命周期Nonce结构体仅包含一个Token string字段代表一次性的唯一 tokenNonces结构体内部用map[string]bool记录已被使用/标记的 tokenNewToken()不断生成新 token直到与已记录的不重复HasToken()/MarkToken()查询与标记 token 是否已用过CheckToken()token 为空返回 No token supplied已被用过则返回 Duplicate submission.CheckThenMarkToken()先校验、再标记一步完成防重放。表单下发与提交校验完整请求链路ch.4.4 示例入口 展示了完整调用链profileHandler在渲染表单时通过submissions.NewNonce()生成全新 token 并注入模板checkProfile处理 POST 提交r.ParseForm()后取r.Form.Get(token)调用submissions.CheckThenMarkToken(token)校验若 token 缺失或已被使用即重复提交/伪造提交立即返回错误否则才继续处理表单数据并交由 validator 做字段校验。模板 profile.gtpl 中正是以隐藏字段下发 tokeninput typehidden nametoken value{{.Token}}/而 submission.gtpl 会在校验失败时逐条列出错误如 Duplicate submission.校验通过才显示 Profile successfully submitted.。运行方式可以参考源文件头部的注释在 main.go 所在目录执行go run main.go然后访问http://localhost:9090即可体验。需要留意的是该示例通过apps/ch.4.4/nonce这类绝对导入路径引用内部包因此需将de/code/src按 GOPATH 布局放置如$GOPATH/src/apps/ch.4.4/...才能正常编译运行服务监听端口在PORT 9090常量中定义可根据实际环境调整。这套实现与 9.1 节的思想完全一致每次渲染表单都下发一个一次性随机数提交时校验其存在性与未使用状态。区别仅在于4.4 节用它防止用户重复提交而 9.1 节把它推广到 CSRF 场景——因为攻击者既不知道也不持有这个随机数自然无法在跨站伪造的请求中携带合法 token请求就会被拒绝。小结跨站请求伪造CSRF是非常危险的 Web 安全问题在 Web 安全圈被称作沉睡的巨人风险等级从这一称号中可见一斑。本节不仅介绍了 CSRF 是什么更剖析了它产生的根源——Web 隐式身份验证机制无法证明请求得到了用户授权——并给出了从服务器端入手的完整防御路径严格区分 GET 与 POST 的语义GET 只读、POST 才允许修改资源从请求方法层面阻断GET 型 CSRF为所有非 GET 请求附加不可预测的随机 token可选择统一 Cookie Token、CAPTCHA、或每个表单独立随机数三种方案其中第三种兼顾安全与体验token 必须与用户会话绑定并妥善保存避免依赖可预测的时间戳且要确保站点本身不存在 XSS 漏洞否则 Cookie 型 token 也会被窃取。如果你希望继续深入 Web 安全主题可以接着阅读本章后续内容确保输入过滤9.2介绍对所有输入数据进行过滤、9.3/9.4 节XSS 与 SQL 注入的防范、9.5 节安全保存密码以及 9.6 节双向加密。本章整体导读可参考 第 9 章安全与加密。相关链接本书日文版目录preface.md上一节安全与加密9.0下一节确保输入过滤9.2方案出处如何防止表单的多次提交4.4仓库配套源码nonce 模块、示例入口 main.go、profile.gtpl、submission.gtpl赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐Build Web Application with Golang 第 6 章精读Go Web 中的 Cookie 与 Session 原理、实战与安全Build Web Application with Golang 第 6 章精读Go Web 中的 Cookie 与 Session 原理、实战与安全 本篇文档教程抵御 CSRF 攻击的 Go 实践指南从原理到令牌防伪实现Build Web Application with Golang抵御 CSRF 攻击的 Go 实践指南从原理到令牌防伪实现Build Web Application with Golang 本文基于《Build Web文档教程CTF-Wiki Web 安全指南跨站请求伪造CSRF原理、攻击类型与防御实战CTF Wiki Web 安全指南跨站请求伪造CSRF原理、攻击类型与防御实战 CSRFCross Site Request Forgery跨站请求伪文档网络安全教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考