为什么 srand(time()) 是坏种子?

Why srand(time()) is a bad seed?

使用 srand(time()) 生成密码重置令牌(或 CSRF 令牌)是错误的,因为令牌是可预测的。

我读了这些:

但我不明白令牌如何可以预测。我知道如果在一秒钟内多次重设密码,我会得到相同的令牌。我有以下代码:

<?php

srand(time());
$reset_password_token = rand(444444444444,999999999999);

?>

如果我在一秒钟内多次重设密码,我知道我得到了相同的令牌,但攻击者如何利用它?

限制了他们暴力破解的范围。例如,如果他们知道有人在最后一分钟重置密码,他们只需要尝试 60 个密码。

但比这更糟。攻击者可以通过为该帐户启动密码重置来进入他们想要的任何帐户。在此之后,他们通过在重置前后的一小段 window 时间内使用 unix 时间戳重复调用 srand 来生成一些令牌,每次递增。这些标记之一必须匹配,除非你的时钟太慢了。

时间框架攻击

攻击者可以know/guess你系统的时间。当然,黑客无法知道确切的秒数,因为对于大多数服务器来说可能会有所不同。

但是比如说你的当地时间是:

> echo time();
1431212010

然后你可以做一个"good guess",种子将位于14312120051431212015之间。

因此,如果您能猜出 10 次,那么密码很可能是正确的。

当然黑客还需要知道"generates"密码的算法。但对于大多数系统来说,这相当简单,而且在安全方面一如既往,最好还是不要对系统了解那么多。毕竟大多数黑客都可以创建自己的帐户,"inspect"密码是如何生成的,然后先寻找模式。

如果黑客有账号him/herself

此外,破解密码的一种非常方便的方法是 post 大约在同一时刻发出两个密码重置请求:假设您有一个帐户 X,并且您想要破解帐户 Y。在一毫秒内,您可以提交两项请求,一项针对您自己,一项针对受害者。接下来您会收到您的密码,您可以将其用于两个帐户。正如@AlfredRossi 所说,您还可以枚举网站的所有帐户,从而破解大多数帐户。

解决方案

大多数系统都提供了生成 "real random" 的方法(当然,我们是否谈论真正的随机性还有待商榷)。例如,通过捕获音频通道的噪音或收听其他 "noise"。这些值较难预测,因为人们很难猜出距 his/her 位置几千英里的音频通道的测量强度。

好的解决方案

这假设需要一个 256 位随机数。

  1. random_bytes(32) (PHP 7.0.0+)
  2. openssl_random_pseudo_bytes(32)
  3. mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)
  4. 正在阅读 /dev/urandom

#4 的代码片段:

<?php
function getRandom($bytes)
{
    // Read only, binary safe mode:
    $fp = fopen('/dev/urandom', 'rb');

    // If we cannot open a handle, we should abort the script
    if ($fp === false) {
        die("File descriptor exhaustion!");
    }
    // Do not buffer (and waste entropy)
    stream_set_read_buffer($fp, 0);

    $entropy = fread($fp, $bytes);
    fclose($fp);
    return $entropy;
}

糟糕的解决方案

  • mt_rand()
  • rand()
  • uniqid()
  • microtime(true)
  • lcg_Value()

什么才是好的解决方案?

一个好的解决方案应该利用加密安全伪随机数生成器 (CSPRNG)。在基于 Unix 的操作系统上,这可以通过直接从 /dev/urandom.

读取来实现

But I don't understand how the token can be predictable.

这个主题之前已经很深入地讨论过了。

I have the following code:

<?php

srand(time());
$reset_password_token = rand(444444444444,999999999999);

?>

理论上,这只有 555555555555 个可能的值。不幸的是,实际数字要低 很多

rand() 使用一种称为线性同余生成器的算法,由于它在 PHP 5 中的实现方式,该算法仅适用于无符号 32 位整数。您提供的两个数字都大于 2**32。我不确定它是否会溢出。 The source code 在这种情况下不是很有启发性。

但是,因为您使用 time() 作为随机数的种子,您将 运行 惹上麻烦。快点,运行 这个代码:

<?php

srand(1431223543);
echo rand()."\n";

您应该会在控制台中看到 1083759687。一般来说,互联网上的计算机之间的时间差异很小。您可能只考虑每个时区最多 2 秒的可能抖动,并且只需要 120 次猜测(最坏情况)就可以开始预测随机数输出。 永远。

对于任何与您的应用程序安全相关的事情,请使用 CSPRNG。

If I reset my password many time in one seconds, I know I get the same token but how a attacker can exploit this?

攻击者一定 "do many of" 错了。攻击者可以在许多不同的秒内生成他们自己的令牌,并针对您的帐户尝试所有令牌。