17

来自维基百科

在密码学中,定时攻击是一种旁道攻击,其中攻击者试图通过分析执行密码算法所花费的时间来破坏密码系统。

实际上,为了防止定时攻击,我使用了从这个答案中获取的以下函数:

function timingSafeCompare($safe, $user) {
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    $safeLen = strlen($safe);
    $userLen = strlen($user);

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) {
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return $result === 0;
}

但我在想是否可以使用随机睡眠来防止这种攻击

function timingSafeCompare($a,$b) {
    sleep(rand(0,100));
    if ($a === $b) {
        return true;
    } else {
        return false;
    }
}

或者也许增加睡眠的随机性

sleep(rand(1,10)+rand(1,10)+rand(1,10)+rand(1,10));

这种方法可以完全防止定时攻击吗?还是只是让工作更加努力?

4

4 回答 4

19

这种方法可以完全防止定时攻击吗?还是只是让工作更加努力?

两者都不。它不会阻止定时攻击,也不会让它们变得更加困难。

要了解原因,请查看sleep 的文档。具体来说,第一个参数的含义:

以秒为单位暂停时间。

因此,您的应用需要 0.3 秒才能在不休眠的情况下做出响应。睡眠需要 0.3、1.3、2.3 等...

所以真的,要得到我们关心的部分(时间差),我们只需要去掉整数部分:

$real_time = $time - floor($time);

但是,让我们更进一步。假设您使用usleep随机睡眠。这要细得多。那是在微秒内睡觉。

嗯,测量是在 15-50纳秒的范围内进行的。因此,睡眠的粒度仍然比正在进行的测量100 倍。所以我们可以平均到一微秒:

$microseconds = $time * 1000000;
$real_microseconds = $microseconds - floor($microseconds);

并且仍然有有意义的数据。

您可以更进一步并使用time_nanosleep,它可以睡眠到纳秒级精度。

然后你就可以开始玩弄数字了。

但是数据还在。随机性的美妙之处在于您可以将其平均:

$x = 15 + rand(1, 10000);

运行足够多的时间,你会得到一个漂亮的图表。你会说大约有 10000 个不同的数字,所以你可以平均掉随机性并推断出“私人”15。

因为表现良好的随机性是无偏的,所以很容易在足够大的样本上进行统计检测。

所以我要问的问题是:

当您可以正确解决问题时,为什么还要打扰类似睡眠的黑客行为?

于 2015-02-12T19:53:59.857 回答
9

如果攻击者可观察到的唯一侧通道是响应时间,则这对于单个请求来说很好。

但是,如果攻击者发出足够多的请求,则此随机延迟可能会平均化,如@Scott引用ircmaxell 博客文章的回答中所述:

因此,如果我们需要运行 49,000 次测试才能获得 15ns 的准确度 [没有随机延迟],那么我们可能需要 100,000 或 1,000,000 次测试才能获得相同的准确度和随机延迟。或者可能是 100,000,000。但是数据还在。

举个例子,让我们估计一个定时攻击需要多少请求才能获得一个有效的 160 位会话 ID,如 PHP ,每个字符 6 位,长度为 27 个字符。假设,就像链接的答案一样,一次只能对一个用户进行攻击(因为他们正在存储用户以在 cookie 中查找)。

以博客文章中的最佳情况 100,000 为例,排列的数量将是100,000 * 2^6 * 27.

平均而言,攻击者会在排列数的中间找到值。

这使得从定时攻击中发现会话 ID 所需的请求数为 86,400,000。这与没有您建议的时序保护的 42,336,000 个请求进行比较(假设 15ns 精度,如博客文章)。

在博客文章中,测试最长的长度 14 平均需要 0.01171 秒,这意味着 86,400,000 需要 1,011,744 秒,相当于 11 天 17 小时 2 分 24 秒。

随机睡眠可以防止定时攻击吗?

这取决于使用随机睡眠的上下文以及它所保护的字符串的位强度。如果它是用于链接问题中的上下文的“让我登录”功能,那么攻击者可能值得花费 11 天时间来使用定时攻击来暴力破解一个值。但是,这是假设条件完美(即,您的应用程序对每个测试的字符串位置的响应时间相当一致,并且没有重置或翻转 ID)。此外,来自攻击者的此类活动会产生大量噪音,并且很可能会通过 IDS 和 IPS 被发现。

它不能完全阻止它们,但它可以使攻击者更难以执行它们。hash-equals假设字符串长度相等,使用类似的东西会更容易和更好地防止定时攻击。

您建议的代码

function timingSafeCompare($a,$b) {
    sleep(rand(0,100));
    if ($a === $b) {
        return true;
    } else {
        return false;
    }
}

请注意,PHPrand函数不是加密安全的:

注意 此函数不会生成加密安全值,不应用于加密目的。如果您需要加密安全值,请考虑openssl_random_pseudo_bytes()改用。

这意味着理论上攻击者可以预测rand将要生成什么,然后使用此信息来确定您的应用程序的响应时间延迟是否是由于随机睡眠造成的。

处理安全性的最佳方法是假设攻击者知道您的源代码——攻击者唯一的秘密应该是密钥和密码之类的东西——假设他们知道所使用的算法和功能。如果您仍然可以说您的系统是安全的,即使攻击者确切地知道它是如何工作的,那么您将大部分时间在那里。像这样的函数rand通常设置为当前时间,因此攻击者可以确保他们的系统时钟设置为与您的服务器相同,然后发出请求以验证他们的生成器是否与您的匹配。

因此,最好避免不安全的随机函数rand,如更改您的实现以使用openssl_random_pseudo_bytes这将是不可预测的。

此外,根据 ircmaxell 的评论,sleep它不够精细,因为它只接受一个整数来表示秒数。如果您打算尝试这种方法,请time_nanosleep使用随机数的纳秒进行调查。

这些指针应该有助于保护您的实现免受这种类型的定时攻击。

于 2015-02-09T09:33:07.670 回答
9

Anthony Ferrara 在他的博文It's All About Time中回答了这个问题。我强烈推荐这篇文章。

许多人在听到定时攻击时会想:“好吧,我只是添加一个随机延迟!这会起作用的!”。它没有

于 2015-02-11T19:21:44.223 回答
1

这种方法可以完全防止定时攻击吗?还是只是让工作更加努力?

ircmaxell 已经回答了为什么这只会使工作更加困难,

但一般来说,防止 PHP 定时攻击的解决方案是

/**
 * execute callback function in constant-time,
 * or throw an exception if callback was too slow
 *
 * @param callable $cb
 * @param float $target_time_seconds
 * @throws \LogicException if the callback was too slow
 * @return whatever $cb returns.
 */
function execute_in_constant_time(callable $cb, float $target_time_seconds = 0.01)
{
    $start_time = microtime(true);
    $ret = ($cb)();
    $success = time_sleep_until($start_time + $target_time_seconds);
    if ($success) {
        return $ret;
    }
    // dammit!
    $time_used = microtime(true) - $start_time;
    throw new \LogicException("callback function was too slow! time expired! target_time_seconds: {$target_time_seconds} actual time used: {$time_used}");
}


使用这种方法,您的代码可能是

function timingSafeCompare($a,$b, float $target_time_seconds = 0.01) {
    return execute_in_constant_time(fn() => $a === $b, $target_time_seconds);
}

缺点是你应该选择一个有很大余量的数字,这意味着在睡眠中浪费了相对多的时间。在我的笔记本电脑上,我不得不使用 0.2(200 毫秒)来比较 2x 恰好为 1-GiB 的字符串,与Core i7-8565U(一个奇怪的我从未听说过的2018中档笔记本电脑CPU)

这个循环:

ini_set("memory_limit", "-1");
$s1 = "a";
$s2 = "a";
$append = str_repeat("a",100*1024);
try {
    for (;;) {
        $res = timingSafeCompare($s1, $s2, 0.01);
        $s1 .= $append;
        $s2 .= $append;
    }
} catch (\Throwable $e) {
    var_dump(strlen($s1));
}

大约 65 兆字节/int(65126401)

(但是您需要多久对 65MB 以上的字符串进行一次恒定时间比较?我想这不经常)

  • 您可能会认为“然后攻击者可以发送一个巨大的字符串进行比较,并检查引发异常需要多长时间”但我认为这不会起作用,===首先检查两个字符串是否具有相同的长度,并且如果它们具有不同的长度,则短路,只有当攻击者可以将两个字符串的长度设置为足够大以超时时,这种攻击才应该起作用

  • 今天我们有原生的 hash_equals() 函数来比较长度完全相同的字符串,但是 hash_equals() 不会保护你免受不同长度的字符串的影响,而上面的函数会。

于 2021-11-12T21:28:00.300 回答