【问题标题】:Why srand(time()) is a bad seed?为什么 srand(time()) 是一个坏种子?
【发布时间】:2015-07-20 15:52:34
【问题描述】:

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

我读过这些:

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

<?php

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

?>

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

【问题讨论】:

  • 种子只是一个问题。使用rand 本身,即使是不可预知的种子,也是一个安全漏洞。

标签: php security random passwords entropy


【解决方案1】:

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

但情况比这更糟。攻击者可以通过为该帐户启动密码重置来进入他们想要的任何帐户。在此之后,他们通过在重置前后的一些小时间窗口内重复调用带有 unix 时间戳的 srand 来生成一些令牌,每次递增。除非您的时钟已经过时,否则其中一个令牌必须匹配。

【讨论】:

  • 假设我确切地知道时间戳并且我知道算法生成器(范围编号,如我的示例)。如果我理解,如果我试试这个:while(1){ //genere a new token and capture the timestamp in the website srand($sametimestamp); $reset_password_token = rand(444444444444,999999999999); //test the token in the website Sleep(3);//for test again with a new timestamp }我能猜到令牌吗?
  • 是的。试试吧。 rand(444444444444,999999999999) 的输出由输入到 srand 的值决定。
  • 我试试这个,它不起作用。我想也许如果我提前 5 分钟生成许多带有时间戳的令牌,也许它可以工作?
  • 这里的“不起作用”是什么意思?你想做什么?
  • 我生成了一个新令牌并捕获了目标网站中的时间戳。在我的服务器中,我生成了一个具有相同 rand 范围的目标 cible 和相同 srand 的令牌,我等待 2/3 秒,然后再试一次。
【解决方案2】:

时间框架攻击

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

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

> echo time();
1431212010

那么您可以“很好地猜测”种子将位于14312120051431212015 之间。

所以如果你能猜到 10 次,密码很可能是正确的。

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

如果黑客有自己的账户

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

解决方案

大多数系统都提供了一种生成“真实随机”的方法(当然,我们是否谈论真实随机还有待商榷)。例如,通过捕获音频通道的噪音或收听其他“噪音”。这些值难以预测,因为人们几乎无法猜测在距离他/她的位置几千英里的音频通道上测得的强度。

【讨论】:

  • Most systems offer a way to generate "real random" (of course it's debatable whether we talk about real random) 更重要的是,you don't want real random 用于密码学。
  • 不会将一个运行的、递增的整数添加到时间种子中来对抗这两种攻击吗?
  • @Wortex17:如果黑客自己有一个帐户,他/她可以尝试重建该计数器。此外,counter 攻击可能是一个糟糕的词选择。安全不是一个开/关的故事,关键是在合理的范围内将风险最小化。
【解决方案3】:

好的解决方案

假设需要 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 的代码 sn-p:

<?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 读取来实现。

但我不明白令牌如何可以预测。

这个话题之前已经很深入了。

我有以下代码:

<?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。

【讨论】:

  • 除了使用/dev/urandom/dev/random 很有趣,因为它试图保证高熵,所以它会阻止读取,直到熵池中有足够的可用位。
  • 通过查找服务器所在的位置从而“猜测”时区也可以减少猜测的次数。如果人们进行一些经过计算的猜测,那么可能需要的猜测会更少。
  • @CommuSoft 您当然是正确的。实际上,您可能会在不到 3 次尝试中猜到它。我提供了 120 作为上限。
【解决方案4】:

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

攻击者必须“做很多”错误的事情。攻击者可以在许多不同的时间内生成自己的令牌,然后针对您的帐户进行全部尝试。

【讨论】:

    猜你喜欢
    • 2020-07-25
    • 1970-01-01
    • 1970-01-01
    • 2016-07-13
    • 2011-07-31
    • 1970-01-01
    • 2021-11-25
    相关资源
    最近更新 更多