ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

《Go Web 编程》第 9.1 节:使用 Go 语言预防 CSRF 跨站请求伪造攻击

《Go Web 编程》第 9.1 节:使用 Go 语言预防 CSRF 跨站请求伪造攻击 文档教程【免费下载链接】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 节整理而成。该仓库是一本介绍如何使用 Go 语言构建 Web 应用的电子书本文聚焦其安全章节中最经典的 CSRF 攻击从攻击原理、危害场景到 Go 服务端的双层防御方案正确使用 GET/POST 非 GET 请求注入伪随机 token逐层展开并辅以仓库de/code目录中的配套源码进行代码级印证帮助读者彻底掌握 CSRF 的成因与防御实现。1. 什么是 CSRF盗用身份的睡着的巨人CSRFCross-site request forgery中文译为跨站请求伪造也被称为 one click attack一次点击攻击或 session riding会话骑乘缩写为 CSRF/XSRF。可以用一句最通俗的话来理解它的危害攻击者盗用你的登录信息以你的身份模拟发送各种请求。攻击者只需要借助少许社会工程学诡计——例如通过 QQ 等聊天软件发送一个链接有些还伪装成短域名用户难以分辨就能迫使 Web 应用的用户去执行攻击者预设的操作。书中给出了一个非常直观的例子当用户登录网络银行查看存款余额在没有退出登录的情况下点击了 QQ 好友发来的链接那么该用户银行账户中的资金就有可能被转移到攻击者指定的账户中。更严重的是CSRF 攻击的受害者一旦是管理员账户攻击者就可以在管理员毫不知情的情况下以管理员身份执行删除数据、修改配置、转账等操作从而危及整个 Web 应用程序。正因如此CSRF 被 Web 安全界称为沉睡的巨人sleeping giant其威胁程度从这个称号便可见一斑。章节定位本节是《Go Web 编程》第九章安全與加密的开篇内容。第 9 章的整体思路是把用户的输入视为不安全数据先过滤9.2 节確保輸入過濾、再防御 CSRF本节、再处理 XSS 与 SQL 注入9.3、9.4 节、最后讲解密码存储与加解密9.5、9.6 节。CSRF 是这一安全体系中最容易被忽视、又最容易造成实际破坏的一环。2. CSRF 的原理一次合法的越权请求是如何产生的要完成一次 CSRF 攻击受害者必须依次完成两个步骤登录受信任网站 A并在本地产生 Cookie在不退出 A 的情况下访问危险网站 B。下图展示了这一攻击过程的完整流程2.1 攻击流程拆解结合上图的角色与步骤一次典型的 CSRF 攻击分为三步受害者浏览器向受信任站点 A如www.example.org发起合法的 HTTP 请求并成功登录浏览器中留下了站点 A 的身份认证 Cookie受信任站点 A 返回的响应页面中嵌入了恶意代码——例如一个指向目标站点stocks.example.org的img标签。由于浏览器在加载 HTML 时会自动请求img的 src 地址用户根本无需点击任何东西受害者的浏览器自动向目标站点stocks.example.org发起该恶意请求图中示例为购买 1000 股 SCOX 股票的转账/交易操作。因为浏览器会自动携带此前访问目标站点时留存的认证 Cookie这个请求会被目标站点误判为受害者的合法操作从而执行恶意操作。2.2 为什么受害者几乎无法避免看到这里读者可能会问如果我不满足以上两个条件中的任意一个就不会受到 CSRF 的攻击。——确实如此但以下情况你无法保证不发生多标签页浏览你不能保证登录一个网站后不再打开一个新的 tab 页面并访问另外的网站尤其是现在浏览器普遍支持多 tabCookie 不会立刻过期你不能保证关闭浏览器后本地 Cookie 立刻过期、上次会话已经结束危险网站可能是可信网站上图中所谓的攻击网站可能是一个存在其他漏洞的、可信任的、经常被人访问的网站。因此对于用户来说很难避免在登录一个网站之后不点击一些链接进行其他操作随时可能成为 CSRF 的受害者。2.3 根因Web 的隐式身份验证机制CSRF 攻击之所以能成立根本原因在于Web 的隐式身份验证机制Web 的身份验证机制Cookie/Session虽然可以保证一个请求确实来自某个用户的浏览器却无法保证该请求是用户批准发送的。也就是说浏览器替我把 Cookie 带上了但服务器无法区分这个带 Cookie 的请求到底是用户主动点击提交的还是被恶意页面借刀杀人自动触发的。这正是 CSRF 与 XSS、SQL 注入等攻击的本质区别CSRF 利用的是服务器对请求来源可信的过度信任。3. 如何预防 CSRF服务端双层防御CSRF 的防御可以从服务端和客户端两方面着手但防御效果以服务端为佳现在一般的 CSRF 防御也都在服务端进行。服务端预防 CSRF 的方式方法有多种但思想上都是相似的主要从以下 2 个方面入手正确使用 GET、POST 和 Cookie——从请求方法的语义上约束让读和写泾渭分明在非 GET 请求中增加伪随机数token——让攻击者无法伪造合法请求的凭据。3.1 第一层防御正确使用 GET、POST 与 Cookie在上一章第 8 章介绍 REST 方式 Web 应用的基础上一般而言普通的 Web 应用都以 GET、POST 为主还有一种请求方式是 Cookie。我们通常按照如下方式设计应用GET常用于查看、列举、展示等不需要改变资源属性的时候POST常用于下达订单、改变一个资源的属性或者做其他一些事情。接下来以 Go 语言举例说明如何限制对资源的访问方法。在基于 mux 路由本书第 3 章介绍的路由机制的代码中mux.Get(/user/:uid, getuser) // 只读操作查询用户信息 mux.Post(/user/:uid, modifyuser) // 写操作修改用户信息这样处理后因为我们限定了修改只能使用 POST当 GET 方式请求时就拒绝响应所以上图中 GET 方式的 CSRF 攻击就可以被防止了。为什么有效图中演示的恶意img标签只能发起 GET 请求浏览器自动加载图片时无法携带自定义 POST body如果修改类接口只接受 POST那么这种图片钓鱼式的攻击请求就会在路由层被直接拒绝。但这样就能全部解决问题了吗当然不是——POST 请求也是可以模拟的。攻击者可以构造一个自动提交的表单或使用 JavaScript 发起跨域 POST同样携带受害者的 Cookie 发起攻击。因此需要实施第二步。3.2 第二层防御在非 GET 请求中增加伪随机数在非 GET 方式的请求中增加随机数大致有三种实现方式方案原理优点缺点方案一统一 Cookie token为每个用户生成一个唯一的 cookie token所有表单都包含同一个伪随机值最简单攻击者无法获得第三方 Cookie理论上表单数据构造失败用户的 Cookie 容易因网站的 XSS 漏洞而被盗取必须确保没有 XSS 漏洞时才安全方案二每个请求使用验证码每次请求都要求用户输入验证码理论上完美攻击者无法绕过需要多次输入验证码用户友好性很差不适合实际应用方案三不同表单包含不同的伪随机值每个表单各自携带唯一的 token兼顾安全与用户体验是实际项目中最常用的方案需要服务端校验 token 的存在性与合法性第三种方案在4.4 小节防止多次提交表單中已经介绍过可直接复用相关代码。它的核心思想与防止表单重复提交完全一致每次渲染表单时生成一个一次性 token提交时校验 token 是否存在且合法。3.3 方案三的完整代码实现第一步生成随机数 tokenh : 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()当前 Unix 时间戳与一个固定盐值salt字符串一起写入 MD5 哈希最终以十六进制字符串形式输出作为 token。第二步在模板中输出 token隐藏字段input typehidden nametoken value{{.}}token 以隐藏字段形式随表单一起提交用户无感知但攻击者无法预知该值。第三步服务端验证 tokenr.ParseForm() token : r.Form.Get(token) if token ! { // 验证 token 的合法性 } else { // 不存在 token 报错 }3.4 破解 token 的可能性理论可行实际几乎不可能这样基本就实现了安全的 POST。也许你会问如果破解了 token 的算法呢按照理论上是可行的但实际上破解是基本不可能的——书中指出有人曾计算过暴力破解该串大概需要2 的 11 次方时间注这是原书对难度极高的定性描述强调其暴力破解成本远超实际攻击收益而非精确的时间量级换算。更关键的是token 方案的正确使用方式并非只验证存在而是要在服务端会话中保存该 token提交时进行比对。正如4.4 小节所强调的我们把这个值儲存到伺服器端session 來控制我們將在第六章講解如何儲存以方便表單提交時比對判定。4. 仓库源码印证从表单防重到 CSRF token 的完整实现本节的核心防御代码并非孤立存在开源仓库de/code目录中提供了配套的可运行示例可以直接印证书中描述的完整调用链。4.1 仓库中的完整示例在 de/code/src/apps/ch.4.4 目录下存放着与 4.4 小节对应的完整实现该实现同样适用于本节的 CSRF token 方案main.goHTTP 服务入口注册/、/profile、/checkprofile三个路由nonce/main.go一次性 tokennonce的核心实现profile.gtpl包含隐藏 token 字段的表单模板submission.gtpl提交结果页面。运行方式仓库注释中已给出说明cd de/code/src/apps/ch.4.4 go run main.go # 然后访问 http://localhost:90904.2 核心调用链拆解① 渲染表单时生成一次性 tokenmain.go 中的profileHandler在渲染表单时调用submissions.NewNonce()生成新 token并通过模板输出func profileHandler(w http.ResponseWriter, r *http.Request) { t.ExecuteTemplate(w, profile, submissions.NewNonce()) }② 表单携带隐藏 tokenprofile.gtpl 中正是书中所写的隐藏字段input typehidden nametoken value{{.Token}}/③ 提交时校验 token 并标记已使用main.go 中的checkProfile在收到 POST 后调用CheckThenMarkTokenfunc checkProfile(w http.ResponseWriter, r *http.Request) { var errs validator.Errors r.ParseForm() token : r.Form.Get(token) if err : submissions.CheckThenMarkToken(token); err ! nil { errs validator.Errors{[]error{err}} } else { // 校验表单数据 } t.ExecuteTemplate(w, submission, errs) }④ token 的生成算法比书中示例更强的随机性nonce/main.go 中的createToken在书中时间戳 固定盐值的基础上额外混入了rand.Int63()随机数使 token 的不可预测性更强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)) }⑤ 一次性nonce语义防重复提交也是 CSRF 的核心保障nonce/main.go 中的CheckThenMarkToken实现了校验通过后立即标记的一次性语义func (n *Nonces) CheckThenMarkToken(token string) error { defer n.MarkToken(token) if err : n.CheckToken(token); err ! nil { return err } return nil }从源码结构看这套机制的价值是双重的对表单防重同一个 token 只能使用一次刷新页面重新提交会被Duplicate submission.错误拒绝见 submission.gtpl 中的提示对 CSRF 防御token 由服务端生成、随表单下发攻击者在用户不知情的情况下无法预知当前表单的 token 值因此即使能构造出 POST 请求也会因为没有合法 token 而被服务端拒绝——这正是书中在非 GET 请求中增加伪随机数思想的工程化落地。补充说明书中 9.1 节给出的示例代码仅演示了token 是否存在的校验骨架if token ! 而仓库 ch.4.4 示例将其补全为完整的存在性 一次性校验。实际生产环境建议在此基础上更进一步将 token 与会话Session绑定存储书中 6.x 章讲解的 Session 机制并在比对时使用subtle.ConstantTimeCompare之类的常量时间比较避免时序侧信道。本节核心价值在于理解伪随机数 服务端校验这一防御范式。5. 总结与防御清单跨站请求伪造CSRF是一种非常危险的 Web 安全威胁被 Web 安全界称为沉睡的巨人。它的成因在于Web 隐式身份验证机制的天然缺陷——服务器能确认请求来自某个浏览器却无法确认请求是用户批准发送的。本节给出的防御建议可以收敛为一份可直接落地的检查清单语义化路由GET 只做查看/列举/展示POST 只做下单、改属性等写操作用路由方法限制堵住图片钓鱼式 GET 攻击参考 zh-tw/09.1.md 中的 mux 示例非 GET 请求注入伪随机 token推荐不同表单携带不同 token方案生成时使用时间戳 盐值生产环境建议再加入随机源通过隐藏字段随表单下发服务端必须校验 token不只是检查是否存在还要比对会话中保存的值并配合一次性nonce语义防止重放警惕配套漏洞token 方案的安全性依赖站点没有 XSS 漏洞这一前提Cookie 一旦被 XSS 盗取统一 token 方案即告失效因此在做好 CSRF 防御的同时仍需配合输入过滤9.2 节確保輸入過濾与 XSS 防护4.3 节預防跨站指令碼学习完整实现可运行示例位于 de/code/src/apps/ch.4.4按注释执行go run main.go即可在http://localhost:9090体验完整流程。本小节不仅对 CSRF 攻击本身进行了介绍还详细说明了造成这种漏洞的原因并给出了防范建议。防御 CSRF 不是某一项单独的技术而是一套请求语义约束 不可预测凭据 服务端强校验的组合拳——希望读者在编写安全的 Go Web 应用时能够将这套思路真正落到实处。延伸阅读仓库内相关章节第九章 安全與加密章节总览4.4 防止多次提交表單token 方案的原始出处4.3 預防跨站指令碼XSS 与 CSRF 的关联防御9.2 確保輸入過濾9.3 避免 SQL 注入9.4 避免 XSS 攻擊赞分享文档教程【免费下载链接】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点击查看免费下载相关推荐如何构建具备长期记忆能力的AI智能体Letta平台深度解析如何构建具备长期记忆能力的AI智能体Letta平台深度解析 还在为大型语言模型的上下文限制而苦恼吗传统AI智能体往往像金鱼一样只有短暂记忆每次对话都需要重Nest.js CSRF防护跨站请求伪造攻击的防御Nest.js CSRF防护跨站请求伪造攻击的防御 1. CSRF攻击原理与风险 跨站请求伪造Cross Site Request ForgeryCSRF后端Web框架Easy-Vibe 后端知识图谱限流与背压原理——从令牌桶算法到多层防护的工程实战Easy Vibe 后端知识图谱限流与背压原理——从令牌桶算法到多层防护的工程实战 ::: tip 导读 本文是 Easy Vibe 后端知识体系 附录 4文档教程上一篇Django REST Framework 测试提速django-test-plus APITestCase 完全指南下一篇NetExec内存优化技术提升性能的实用方法创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表