每个请求是否需要新的CSRF令牌?

51

所以我在阅读相关内容时,对于需要CSRF令牌这一部分感到非常困惑,不知道是每个请求都应该生成一个新的令牌,还是每小时或其他时间间隔生成一个新的令牌?

$data['token'] = md5(uniqid(rand(), true));
$_SESSION['token'] = $data['token'];

假设最好每小时生成一个令牌,那么我需要两个会话:令牌、过期时间。

如何将它放在表单中呢?只需将echo $_SESSION ['token']放在隐藏值表单上,然后在提交时进行比较即可吗?


3
请注意,每个请求生成一个新令牌可能会导致问题,如果用户同时在两个不同的浏览器窗口中打开您的应用程序,则令牌将失去同步。 - Michael Berkowski
@Michael,那个问题可以通过推送技术和HTML5通信标准来解决。 - Cata Cata
2
如果不涉及高安全性情况,可以对用户IP和密码哈希值进行散列,这种方式可能永远不会或很少更改,但仍然可以达到其目的,因为攻击者无法知道用户的密码(如果攻击者已知密码,则无需对其执行CSRF攻击)。 - ThiefMaster
是的,这样您就不需要存储令牌。而且它不经常更改=>更适合使用后退按钮和多个标签页的用户。 - ThiefMaster
请参阅以下链接:https://dev59.com/Dmoy5IYBdhLWcg3wScFB,https://dev59.com/NWs05IYBdhLWcg3wIObS和http://security.stackexchange.com/questions/22903/why-refresh-csrf-token-per-form-request。 - Lode
显示剩余3条评论
5个回答

42
如果您按表单请求执行此操作,则基本上会消除 CSRF 攻击的能力,并且您可以解决另一个常见问题:多个表单提交。简单来说,如果用户在提交之前要求填写表单,则您的应用程序只接受表单输入。
正常情况下,用户 A 访问您的网站并请求表单 A,获得表单 A 以及表单 A 的唯一代码。当用户提交表单 A 时,必须包括仅适用于表单 A 的唯一代码。
CSRF 攻击的情况下,用户 A 访问您的网站并请求表单 A。同时,他们访问另一个“坏”网站,该网站试图对其进行 CSRF 攻击,使其提交假表单 B。
但是,您的网站知道用户 A 从未请求过表单 B,因此即使他们有表单 A 的唯一代码,表单 B 也会被拒绝,因为他们没有表单 B 的唯一代码,只有表单 A 的代码。您的用户是安全的,您可以放心睡觉。
但是,如果您将其作为持续一个小时(如您上面发布的内容)的通用令牌,则上述攻击可能有效,在这种情况下,您的 CSRF 保护不起作用。这是因为应用程序不知道首先没有请求表单 B。这是通用令牌。CSRF 预防的整个重点是使每个表单令牌对该表单唯一。
编辑:因为您要求更多信息: 1-您不必每个表单请求执行此操作,您可以根据小时/会话等来执行此操作。关键点是向用户提供的秘密值,并在返回时重新提交。另一个网站不知道这个值,因此不能提交虚假表单。
因此,您可以每个请求或每个会话生成令牌:
// Before rendering the page:
$data['my_token'] = md5(uniqid(rand(), true));
$_SESSION['my_token'] = $data['my_token'];

// During page rendering:
<input type="hidden" name="my_token" id="my_token" value="<? php echo $_SESSION['my_token']?>" />

// After they click submit, when checking form:
if ($_POST['my_token'] === $_SESSION['my_token'])
{
        // was ok
}
else
{
          // was bad!!!
}

而且因为它是“每个表单” - 您不会收到重复的表单提交 - 因为您可以在第一次表单提交后清除令牌!


18
“那么上面的攻击可能会奏效”——如何?请详细说明。(注意:原回答不正确,因为回答者不知道CSRF令牌的用途。) - zerkms
18
攻击者如何得知CSRF令牌的值?“CSRF预防的整个重点是使每个表单令牌都唯一对应于该表单” --- 不,你错了。 - zerkms
4
表单应该包含 csrf-token,这是基于 csrf-token 的保护机制的工作原理。攻击者无法知道它。在继续讨论之前,我建议您对此进行一些了解。 - zerkms
1
我看到了将其包含在每个请求中的短语,但我没有看到要求令牌在每个请求中不同。你在那个页面上看到了吗?那么,我的错误在哪里?在你想将它们用作证明之前,请仔细阅读这些链接。 - zerkms
8
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/10926/discussion-between-laurencei-and-zerkms - Laurence
显示剩余11条评论

24

通常,每个用户或会话只需要一个令牌即可。重要的是,该令牌仅绑定到一个特定的用户/会话,而不是全局使用。

如果您担心令牌可能会泄漏到攻击站点(例如通过XSS),您可以将令牌的有效性限制为某个特定时间段、某个表单/URL或某个使用次数。

但是,限制有效性的缺点是可能会导致误报,从而影响可用性,例如合法请求可能会使用已经失效的令牌,因为令牌请求时间太长或者令牌已经过多地使用。

因此,我的建议是每个用户/会话只使用一个令牌。如果您需要更高的安全性,每个用户/会话每个表单/URL使用一个令牌,这样如果某个表单/URL的令牌泄漏了,则其他令牌仍然安全。


请定义“每个用户”。这是什么意思?什么样的用户才算数?您是否在指IP地址跟踪用户? - hakre
1
@hakre 通常情况下,您会使用会话将令牌与会话关联起来。但是如果有用户帐户,您也可以直接将令牌与用户关联起来。这就是我所说的“按用户”。 - Gumbo
而会话呢?由cookie标识?那不会在上下文中留下漏洞吗?我的意思是只要会话标识符可以在请求中传递,这就行不通了,对吧?虽然提供的信息对指导很有用,但只是想绕个弯。 - hakre
1
@hakre 不是的,会话只是用来将令牌与请求关联起来,两者必须相等才能使请求得到认证。 - Gumbo
@Gumbo 这不也是一个好主意吗?在任何成功的表单提交或用户权限更改后重新生成令牌。 - Kid Diamond
1
只是想补充一下,我已经在每个请求的基础上使用CSRF令牌,并且正在转向每个用户/会话。我遇到了假阳性的问题,这可能是由于将CSRF令牌存储在会话中的映射中引起的并发问题。未来,我计划通过诸如CSP等标准保持紧密的XSS安全,并使CSRF简单化。 - Jake88

3
你的问题的答案是:这取决于情况。
对于计时令牌,您不需要使用会话,只需在服务器上使用服务器时间和密钥即可。
但是,假设最好每小时生成一个令牌,那么我将需要两个会话:令牌、过期时间。
不,您需要编写程序,能够为一段时间生成令牌。比如说,你按照 30 分钟的时间段来划分时间。然后你可以创建当前 30 分钟形式的一个令牌。
当表单被提交时,您可以验证令牌的有效性,对比目前时间和以前的 30 分钟时间段。因此,一个令牌的有效期为 30 分钟至一小时。
$token = function($tick = 0) use($secret, $hash) {
    $segment = ((int) ($_SERVER['REQUEST_TIME'] / 1800)) + $tick;
    return $hash($secret . $segment);
};

@John 根据你的需求,如果基于 $_SESSION 的方法适合你,就用它吧。我曾经用它来保护下载链接,效果很好。 - hakre
@john:我添加了一个示例函数,演示如何生成在不使用会话的情况下有效期为特定时间段的令牌。 - hakre
1
CSRF令牌的TTL为30分钟与24小时和1分钟的TTL令牌一样安全。但是,TTL越小,用户发现CSRF令牌失效的机会就越大。因此,我主张每个会话使用1个CSRF令牌,无需失效/重新生成。 - zerkms
-1 这并不能防止 CSRF 攻击。攻击者只需要自己从服务器获取一个有效的令牌即可。 - Gumbo
@Gumbo:如果您创建密钥并将其存储在Cookie中,那就不会有问题——最好甚至只存储其中的一部分。 - hakre
显示剩余6条评论

2

owasp网站上提到:

与每个会话令牌相比,每个请求令牌更安全,因为攻击者利用窃取的令牌的时间范围很短。但是,这可能会导致可用性问题。

并且结论是:

CSRF令牌应该满足以下要求:

  • 每个用户会话唯一。
  • 保密。
  • 不可预测(由安全方法生成的大随机值)。

0

您可以使用两种方法。

  1. 为每个请求生成一个随机令牌。为了解决令牌不同步的问题,您可以使用服务器发送事件http://www.w3.org/TR/eventsource/或WebSocketshttp://www.w3.org/TR/websockets/通信技术实时更新每个页面的令牌。

  2. 您可以为每个用户会话使用一个令牌(无需每小时执行一次),这更容易实现。您不会遇到同步问题,但如果令牌不太强,则可能被猜测。当然,如果令牌非常随机,则恶意人员实际上很难进行请求伪造。

P.S. 第一种方法最安全,但更难实现并且使用更多资源。


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