英文:
CSRF in Go Web Applications
问题
我想在我的Go Web应用程序中实现CSRF防护。用户不登录,但会填写表单并通过Stripe Checkout进行支付。
提交表单会在会话变量(cookie)中设置一个键,以便他们以后可以编辑自己发布的内容,并且在电子邮件中的URL允许他们在cookie过期后返回并再次进行编辑(如果需要)。
从我所看到的,我可以使用https://code.google.com/p/xsrftoken/和“双重提交cookie”方法来实现CSRF防护,具体步骤如下:
-
根据任意用户ID(使用uuid.V4()通过go-uuid生成)生成一个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
与会话中的令牌是否匹配,如果匹配,则使用xsrftoken包验证其是否过期: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分钟,他们暂时离开了一会儿)将他们重定向回表单(当然要重新填充!)并生成新的会话ID + CSRF令牌?
-
是否有其他方法可以实现这一点?Coding Horror指出,SO为发送给客户端的每个HTML表单生成一个唯一键。如果我想使用xsrftoken包采用这种方法,我该如何操作,考虑到它在生成新键时需要一个userID?
-
我还忽略了什么其他问题吗?
英文:
I want to implement CSRF prevention in my Go web application. Users don't log in, but they do fill out forms and pay (via Stripe Checkout).
Posting something sets a key in a session variable (cookie) so they can later edit what they've posted, and a URL in an email allows them to come back when the cookie has expired and edit it again if need be.
From what I can see, I can use https://code.google.com/p/xsrftoken/ with the "double submitted cookie" method to implement CSRF prevention by:
-
Generate a CSRF token against an arbitrary user ID (uuid.V4() via go-uuid), like so:
if session.Values["id"] == "" { session.Values["id"] = uuid.NewV4() } csrfToken := xsrftoken.Generate(csrfKey, session.Values["id"], "/listing/new/post")
-
... and store that in the session and render it in a hidden field in the template:
session.Values["csrfToken"] = csrfToken ... <input type="hidden" id="_csrf" value={{ .csrfToken }}>
-
When the user submits the form, I need to get the ID I generated, confirm that the submitted
csrfToken
from the form matches the one in the session, and if so, validate it with the xsrf package to confirm it hasn't expired: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) }
My pertinent questions are:
-
Should I generate a new token every time the form is rendered? Or is it acceptable to re-use a non-expired token for a single user session? Update: According to this answer I should only generate a new token per session (i.e. so the same user gets the same token on the same form, until the token expires)
-
Given the updated question, how do I handle the situation where a created token expires between the time the user requests the form and then submits the form? (perhaps it had 10 minutes left, and they alt+tabbed out for a while) Re-direct them back to the form (re-populated, of course!) and generate a new session id + csrf token?
-
Is there a different way to do this? Coding Horror indicates that SO generates a unique key for every HTML form sent to the client? How would I go about going down this route with the xsrf package, given that it wants a userID when generating a new key?
-
What else have I overlooked?
答案1
得分: 6
每次渲染表单时都生成一个新的令牌是一个好主意。给定一个持久的攻击者和一个可行的入口向量,只是时间的问题,直到攻击者获得两者。然而,如果在攻击者能够破解当前令牌之前,至少有一个标识符重新生成,那就没有问题。
对于在用户请求表单和提交表单之间令牌过期的情况,你可以通过AJAX更新cookie和CSRF令牌,以便为客户端提供更长的时间来填写表单。
如果你想要为每个发送给客户端的HTML表单生成一个唯一的密钥,可以考虑使用xsrf包。不过,由于xsrf包在生成新密钥时需要一个用户ID,你需要找到一种方法来为每个用户生成唯一的ID。
英文:
> Should I generate a new token every time the form is rendered? Or is
> it acceptable to re-use a non-expired token for a single user session?
> Update: According to this answer I should only generate a new token
> per session (i.e. so the same user gets the same token on the same
> form, until the token expires)
It is a good idea to regenerate both the token and session ID often. Given a persistent attacker and a viable entry vector, it's just a matter of time until the attacker obtains both. If, however, at least one of both identifiers regenerates before the attacker is able to crack the current one, then no problem.
> Given the updated question, how do I handle the situation where a
> created token expires between the time the user requests the form and
> then submits the form? (perhaps it had 10 minutes left, and they
> alt+tabbed out for a while) Re-direct them back to the form
> (re-populated, of course!) and generate a new session id + csrf token?
You can update cookies and CSRF tokens through AJAX if you want to give your client vast time to fill out a form.
> Is there a different way to do this? Coding Horror indicates that SO
> generates a unique key for every HTML form sent to the client? How
> would I go about going down this route with the xsrf package, given
> that it wants a userID when generating a new key?
The more tightly bound a token is to a certain action that requires authentication, the more fine-grained control you have. If you can uniquely identify each form in your application then I'd say do it.
答案2
得分: 4
我为Go语言创建了一个名为nosurf的CSRF保护包。以下是它处理你提到的几个方面的方式:
- 令牌是通过从CS PRNG获取字节并使用base64进行编码来创建的。它不会在每次页面加载或每个表单中重新生成,但可以通过用户可调用的函数重新生成令牌。
- 然后将令牌存储在一个cookie中(不是会话,因为它是一个通用的中间件,不仅适用于特定的框架)。该cookie的有效期为一年,但你可以轻松修改此持续时间。
nosurf
负责取消请求,并返回403或调用自定义的失败处理程序(如果设置了)。你不需要在代码中编写if CsrfCheckOk(r) { ... }
或类似的内容。- 不幸的是,它无法解决页面加载和表单提交之间令牌过期的问题。
这就是它的工作方式,尽管我不确定这是否是处理CSRF的最佳方式。针对特定框架的包可能在某些方面由于紧密集成而处理得更好。
英文:
I've created a CSRF protection package for Go called nosurf. Here's how it handles the areas you mentioned:
- Token is created by taking bytes from CS PRNG and encoding them using base64. It is not regenerated for every page load or every form, though there is a user-callable function for regenerating the token.
- It is then stored in a cookie (not a session, as it's a generic middleware not intended for any specific framework only). The cookie lasts one year, but you can easily modify this duration.
nosurf
takes care of cancelling the request and either returning 403 or calling your custom failure handler (if set). You don't have to haveif CsrfCheckOk(r) { ... }
or anything like that in your code.- Sadly, it doesn't address token expiring inbetween the page load and the form submission.
So that's it, even though I'm not sure it is the best way to handle CSRF all-around. A package for a specific framework might handle it better in some ways due to tight integration.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论