在会话变量和表单中存储随机数来进行CSRF保护

10

为了防止 CSRF 攻击,您应该在表单中的隐藏字段和 cookie 或会话变量中放置一个 nonce。但是,如果用户在不同的选项卡中打开了几个页面呢?在这种情况下,每个选项卡都会使用唯一的 nonce 填充表单,但是会话变量或 cookie 中只有一个 nonce。或者,如果尝试将所有 nonce 存储在 cookie/session 变量中,又如何确定哪个 nonce 属于哪个表单呢?

4个回答

7
您可以在每个表单中存储相同的随机数。最简单的方法是将随机数与会话ID绑定,以便这些表单仅在该会话中起作用。
您会希望让攻击者难以窃取会话ID并创建自己的随机数。因此,一种方法是使用HMAC-SHA256(或类似算法)对会话ID进行哈希,使用不公开的密钥。
(显然,如果攻击者可以获得实际的会话ID本身,则已经可以进行会话劫持。所以我谈论的不是那个,而是攻击者能够创建一个脚本(在受害者的计算机上运行),以某种方式抓取会话ID并使用其动态生成预填充随机数的URL。)
估计时间:以上方法是否足够取决于您期望的典型会话持续时间。如果用户通常使用超过几个小时的长期会话,则需要使用更复杂的方法。
一种方法是为每个表单创建一个新的随机数,其中包含时间戳以及hash(timestamp.sessionid)(其中hash是上面描述的HMAC算法的某个变体,以防止伪造,并且“。”是字符串连接)。然后通过以下方式验证随机数:
1. 检查时间戳以确保随机数足够新鲜(这取决于您的策略,但几个小时是典型的时间)。 2. 然后,根据时间戳和会话ID计算哈希值,并将其与随机数进行比较,以验证随机数是否真实有效。
如果随机数检查失败,您需要显示一个新表单,其中包含用户提交的内容(这样,如果他们花了一整天来编写帖子,他们不会失去所有艰苦的努力),以及一个新的随机数。然后用户就可以立即成功地重新提交。

那么你每次不会生成一个新的随机数吗?在整个会话期间和多次提交期间,随机数将保持不变。 - Marius
@Marius:我会更新我的答案以回应你的评论。 :-) - C. K. Young
-1 攻击者并没有会话 ID,浏览器才有。这不是 HMAC 的工作。 - rook
如果会话 ID 在论坛中,那又有什么关系呢?如果攻击者能够读取论坛,那么攻击者就可以读取 JavaScript:document.cookie。我认为你不知道自己在保护什么。 - rook
@Rock 我认为攻击者必须有一个XSS漏洞才能读取javascript:document.cookie。(并且假设没有XSS漏洞。) - KajMagnus
3
Nonce代表“仅限使用一次的数字”,如果您使用它超过一次,那么就会打破整个目的。 - Chad Wilson

4

有些人为每个表单生成一个令牌,这是一种非常安全的方法。然而,这可能会破坏您的应用程序并惹恼用户。为了防止对您的站点进行所有XSRF攻击,您只需要为每个会话设置唯一的1个令牌变量,然后攻击者将无法伪造任何请求,除非他能找到这1个令牌。这种方法的小问题是,只要受害者访问攻击者控制的网站,攻击者就可以暴力破解此令牌。但是,如果令牌相当大,例如32字节左右,则需要很多年才能暴力破解,并且http会话在那之前应该已经过期。


攻击者难道不可以请求表单以获取有效的令牌吗?看起来非常简单。 - Chris Moschini
1
@Chris Moschini,这不是XSRF的工作方式。你正在使用别人的会话而不是自己的,这根本就没有意义。 - rook
谢谢。如果服务器在反向代理后面,并且每个请求都会更新HTTP会话,该怎么办? - Prof. Falken

3
您所描述的不再是一次性号码(nonce = number used once),而只是一个会话标识符。一次性号码的整个意义在于它仅对单个表单提交有效,因此比仅使用会话ID更具有防止劫持的安全性,但代价是无法在网站上同时运行多个选项卡。
对于许多目的来说,一次性号码都过于复杂了。如果您使用它们,应该仅在对系统进行重要更改的表单上设置和要求它们,并教育用户他们不能同时使用多个这样的表单。未设置一次性号码的页面应注意不清除会话中先前存储的任何一次性号码,以便用户仍然可以在非一次性号码页面与一次性号码表单并行使用。

0

很久以前写了这篇文章。 我已经实现了一个几乎可以保护的csrf阻止器。 它可以在多个打开的窗口中运行,但我仍在评估它所提供的保护类型。它使用DB方法,即将存储到表而不是会话。 注意:在这种情况下,我使用MD5作为一种简单的反SQLi机制。

伪代码:

表格:

token = randomstring #to be used in form hidden input
db->insert into csrf (token, user_id) values (md5(token),md5(cookie(user_id))

——令牌将被保存在数据库中,直到从下面的操作脚本中访问:

操作脚本:

if md5(post(token)) belongs to md5(cookie(user_id)) 
    #discard the token
    db -> delete from csrf where token=md5(post(token)) and user_id=md5(cookie(user_id)) 
    do the rest of the stuff

将用户 ID 添加到记录实际上并不增加安全性,但它允许在断开连接时从数据库中删除用户未使用的令牌。 - Ruby Racer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接