5

我想在我的 Go Web 应用程序中实现 CSRF 预防。用户无需登录,但他们会填写表格并付款(通过 Stripe Checkout)。

发布内容会在会话变量 (cookie) 中设置一个密钥,以便他们稍后可以编辑他们发布的内容,并且电子邮件中的 URL 允许他们在 cookie 过期时返回并在需要时再次编辑它。

据我所知,我可以使用https://code.google.com/p/xsrftoken/和“双重提交 cookie”方法来实现 CSRF 预防:

  • 针对任意用户 ID(通过go-uuid uuid.V4())生成 CSRF 令牌,如下所示:

    if session.Values["id"] == "" {
    session.Values["id"] = uuid.NewV4()
    }
    
    csrfToken := xsrftoken.Generate(csrfKey, session.Values["id"], "/listing/new/post")
    
  • ...并将其存储在会话中并将其呈现在模板中的隐藏字段中:

    session.Values["csrfToken"] = csrfToken
    ...
    <input type="hidden" id="_csrf" value={{ .csrfToken }}>
    
  • 当用户提交表单时,我需要获取我生成的 ID,确认csrfToken从表单提交的与会话中的匹配,如果是,则使用 xsrf 包进行验证以确认它没有过期:

    userID := session.Values["id"]
    
    if session.Values["csrfToken"] != r.PostFormValue("csrfToken") {
    http.Redirect(w, r, "/listing/new", 400)
    }
    
    if !xsrftoken.Valid(session.Values["csrfToken"], csrfKey, userID, "/listing/new/post") {
    http.Redirect(w, r, "/listing/new", 400)
    }
    

我的相关问题是:

  • 每次呈现表单时我是否应该生成一个新令牌?或者是否可以为单个用户会话重新使用未过期的令牌? 更新:根据这个答案,我应该只为每个会话生成一个新令牌(即同一个用户在同一个表单上获得相同的令牌,直到令牌过期)

  • 鉴于更新的问题,我该如何处理在用户请求表单和提交表单之间创建的令牌过期的情况?(也许还剩 10 分钟,他们 alt+tab 了一段时间)将它们重定向回表单(当然是重新填充!)并生成一个新的会话 id + csrf 令牌?

  • 有不同的方法可以做到这一点吗?Coding Horror表示 SO 会为发送给客户端的每个 HTML 表单生成一个唯一的密钥?考虑到在生成新密钥时它需要一个用户 ID,我将如何使用 xsrf 包沿着这条路线走下去?

  • 我还忽略了什么?

4

2 回答 2

6

每次呈现表单时我是否应该生成一个新令牌?或者是否可以为单个用户会话重新使用未过期的令牌?更新:根据这个答案,我应该只为每个会话生成一个新令牌(即,同一个用户在同一个表单上获得相同的令牌,直到令牌过期)

经常重新生成令牌和会话 ID 是个好主意。给定一个持久的攻击者和一个可行的入口向量,攻击者获得两者只是时间问题。但是,如果在攻击者能够破解当前标识符之前,两个标识符中的至少一个重新生成,那么没有问题。

鉴于更新的问题,我该如何处理在用户请求表单和提交表单之间创建的令牌过期的情况?(也许还剩 10 分钟,他们 alt+tab 了一段时间)将它们重定向回表单(当然是重新填充!)并生成一个新的会话 id + csrf 令牌?

如果您想让您的客户有大量时间填写表格,您可以通过 AJAX 更新 cookie 和 CSRF 令牌。

有不同的方法可以做到这一点吗?Coding Horror 表示 SO 会为发送给客户端的每个 HTML 表单生成一个唯一的密钥?考虑到在生成新密钥时它需要一个用户 ID,我将如何使用 xsrf 包沿着这条路线走下去?

令牌与需要身份验证的特定操作的绑定越紧密,您拥有的控制就越细。如果您可以唯一标识应用程序中的每个表单,那么我会说这样做。

于 2013-10-02T09:26:10.573 回答
4

我为 Go 创建了一个名为nosurf的 CSRF 保护包。以下是它处理您提到的区域的方式:

  • 令牌是通过从 CS PRNG 中获取字节并使用 base64 对其进行编码来创建的。它不会为每个页面加载或每个表单重新生成,尽管有一个用户可调用的函数来重新生成令牌。
  • 然后将其存储在 cookie 中(不是会话,因为它是一个通用中间件,并非仅适用于任何特定框架)。cookie 持续一年,但您可以轻松修改此持续时间。
  • nosurf负责取消请求并返回 403 或调用您的自定义故障处理程序(如果已设置)。您不必if CsrfCheckOk(r) { ... }在代码中具有或类似的东西。
  • 遗憾的是,它没有解决在页面加载和表单提交之间过期的令牌。

就是这样,即使我不确定它是否是全方位处理 CSRF 的最佳方式。由于紧密集成,特定框架的包在某些方面可能会更好地处理它。

于 2013-10-03T14:12:50.630 回答