为什么 srand(time()) 是坏种子?
Why srand(time()) is a bad seed?
使用 srand(time())
生成密码重置令牌(或 CSRF 令牌)是错误的,因为令牌是可预测的。
我读了这些:
Is using microtime() to generate password-reset tokens bad practice
REST Web Service authentication token implementation
但我不明白令牌如何可以预测。我知道如果在一秒钟内多次重设密码,我会得到相同的令牌。我有以下代码:
<?php
srand(time());
$reset_password_token = rand(444444444444,999999999999);
?>
如果我在一秒钟内多次重设密码,我知道我得到了相同的令牌,但攻击者如何利用它?
限制了他们暴力破解的范围。例如,如果他们知道有人在最后一分钟重置密码,他们只需要尝试 60 个密码。
但比这更糟。攻击者可以通过为该帐户启动密码重置来进入他们想要的任何帐户。在此之后,他们通过在重置前后的一小段 window 时间内使用 unix 时间戳重复调用 srand 来生成一些令牌,每次递增。这些标记之一必须匹配,除非你的时钟太慢了。
时间框架攻击
攻击者可以know/guess你系统的时间。当然,黑客无法知道确切的秒数,因为对于大多数服务器来说可能会有所不同。
但是比如说你的当地时间是:
> echo time();
1431212010
然后你可以做一个"good guess",种子将位于1431212005
和1431212015
之间。
因此,如果您能猜出 10 次,那么密码很可能是正确的。
当然黑客还需要知道"generates"密码的算法。但对于大多数系统来说,这相当简单,而且在安全方面一如既往,最好还是不要对系统了解那么多。毕竟大多数黑客都可以创建自己的帐户,"inspect"密码是如何生成的,然后先寻找模式。
如果黑客有账号him/herself
此外,破解密码的一种非常方便的方法是 post 大约在同一时刻发出两个密码重置请求:假设您有一个帐户 X,并且您想要破解帐户 Y。在一毫秒内,您可以提交两项请求,一项针对您自己,一项针对受害者。接下来您会收到您的密码,您可以将其用于两个帐户。正如@AlfredRossi 所说,您还可以枚举网站的所有帐户,从而破解大多数帐户。
解决方案
大多数系统都提供了生成 "real random" 的方法(当然,我们是否谈论真正的随机性还有待商榷)。例如,通过捕获音频通道的噪音或收听其他 "noise"。这些值较难预测,因为人们很难猜出距 his/her 位置几千英里的音频通道的测量强度。
好的解决方案
这假设需要一个 256 位随机数。
random_bytes(32)
(PHP 7.0.0+)
openssl_random_pseudo_bytes(32)
mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)
- 正在阅读
/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.
这个主题之前已经很深入地讨论过了。
- Pwning RNGs (PDF)
- Predicting Random Numbers in PHP - It's Easier Than You Think
- Insufficient Entropy for Random Values
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" 错了。攻击者可以在许多不同的秒内生成他们自己的令牌,并针对您的帐户尝试所有令牌。
使用 srand(time())
生成密码重置令牌(或 CSRF 令牌)是错误的,因为令牌是可预测的。
我读了这些:
Is using microtime() to generate password-reset tokens bad practice
REST Web Service authentication token implementation
但我不明白令牌如何可以预测。我知道如果在一秒钟内多次重设密码,我会得到相同的令牌。我有以下代码:
<?php
srand(time());
$reset_password_token = rand(444444444444,999999999999);
?>
如果我在一秒钟内多次重设密码,我知道我得到了相同的令牌,但攻击者如何利用它?
限制了他们暴力破解的范围。例如,如果他们知道有人在最后一分钟重置密码,他们只需要尝试 60 个密码。
但比这更糟。攻击者可以通过为该帐户启动密码重置来进入他们想要的任何帐户。在此之后,他们通过在重置前后的一小段 window 时间内使用 unix 时间戳重复调用 srand 来生成一些令牌,每次递增。这些标记之一必须匹配,除非你的时钟太慢了。
时间框架攻击
攻击者可以know/guess你系统的时间。当然,黑客无法知道确切的秒数,因为对于大多数服务器来说可能会有所不同。
但是比如说你的当地时间是:
> echo time();
1431212010
然后你可以做一个"good guess",种子将位于1431212005
和1431212015
之间。
因此,如果您能猜出 10 次,那么密码很可能是正确的。
当然黑客还需要知道"generates"密码的算法。但对于大多数系统来说,这相当简单,而且在安全方面一如既往,最好还是不要对系统了解那么多。毕竟大多数黑客都可以创建自己的帐户,"inspect"密码是如何生成的,然后先寻找模式。
如果黑客有账号him/herself
此外,破解密码的一种非常方便的方法是 post 大约在同一时刻发出两个密码重置请求:假设您有一个帐户 X,并且您想要破解帐户 Y。在一毫秒内,您可以提交两项请求,一项针对您自己,一项针对受害者。接下来您会收到您的密码,您可以将其用于两个帐户。正如@AlfredRossi 所说,您还可以枚举网站的所有帐户,从而破解大多数帐户。
解决方案
大多数系统都提供了生成 "real random" 的方法(当然,我们是否谈论真正的随机性还有待商榷)。例如,通过捕获音频通道的噪音或收听其他 "noise"。这些值较难预测,因为人们很难猜出距 his/her 位置几千英里的音频通道的测量强度。
好的解决方案
这假设需要一个 256 位随机数。
random_bytes(32)
(PHP 7.0.0+)openssl_random_pseudo_bytes(32)
mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)
- 正在阅读
/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.
这个主题之前已经很深入地讨论过了。
- Pwning RNGs (PDF)
- Predicting Random Numbers in PHP - It's Easier Than You Think
- Insufficient Entropy for Random Values
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" 错了。攻击者可以在许多不同的秒内生成他们自己的令牌,并针对您的帐户尝试所有令牌。