Может ли случайный сон предотвратить атаки по времени?

Из Википедии

В криптографии атака по времени представляет собой атаку по побочному каналу, в которой злоумышленник пытается взломать криптосистему, анализируя время, необходимое для выполнения криптографических алгоритмов.

На самом деле, чтобы предотвратить атаки по времени, я использую следующую функцию, взятую из этого ответа:

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 ответа

Решение

Такой подход может полностью предотвратить время атаки? Или просто сделать работу тяжелее?

Ни. Это не предотвращает временные атаки и не делает их более сложными.

Чтобы понять почему, посмотрите на документы для сна. Конкретно значение первого параметра:

Время остановки в секундах.

Таким образом, ваше приложение занимает 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.

Поскольку случайность с хорошим поведением беспристрастна, статистически довольно легко обнаружить ее на достаточно большой выборке.

Итак, вопрос, который я хотел бы задать:

Зачем беспокоиться о подобных взломах, когда вы можете исправить проблему правильно?

Энтони Феррара ответил на этот вопрос в своем блоге "Всё о времени". Я очень рекомендую эту статью.

Многие люди, когда они слышат о времени атаки, думают: "Ну, я просто добавлю случайную задержку! Это сработает!". И это не так.

Это хорошо для одного запроса, если атакующим является единственный боковой канал, который является временем отклика.

Однако, если злоумышленник делает достаточно запросов, эта случайная задержка может усредняться, как отмечено в ответе @Scott со ссылкой на сообщение в блоге ircmaxell:

Таким образом, если бы нам нужно было выполнить 49 000 тестов, чтобы получить точность 15 нс [без случайной задержки], то нам потребовалось бы, возможно, 100 000 или 1 000 000 тестов для той же точности со случайной задержкой. Или, может быть, 100 000 000 Но данные все еще там.

В качестве примера, давайте оценим количество запросов, которое может понадобиться атаке по времени, чтобы получить действительный 160-битный идентификатор сеанса, такой как PHP, с 6 битами на символ, который дает длину 27 символов. Предположим, как и связанный ответ, что атака может быть осуществлена ​​только на одного пользователя одновременно (так как они хранят пользователя для поиска в cookie).

Если взять самый лучший случай из сообщения в блоге "100 000", то число перестановок будет 100,000 * 2^6 * 27,

В среднем злоумышленник найдет значение в середине количества перестановок.

Это дает число запросов, необходимых для обнаружения идентификатора сеанса от временной атаки, равное 86 400 000. Это сравнивается с 42 336 000 запросов без предложенной защиты по времени (при условии точности 15 нс, как в блоге).

В сообщении блога, взяв самую длинную из протестированных 14, потребовалось в среднем 0,01171 секунды, что означает, что 86 400 000 потребуется 1 011 744 секунды, что соответствует 11 дням 17 часам 2 минутам 24 секундам.

Может ли случайный сон предотвратить атаки по времени?

Это зависит от контекста, в котором используется ваш случайный сон, и от битовой силы строки, которую он защищает. Если это функция "держать меня в системе", которая является контекстом в связанном вопросе, то злоумышленнику может потребоваться потратить 11 дней, чтобы использовать атаку по времени для грубой форсировки значения. Тем не менее, это предполагает идеальные условия (т. Е. Достаточно постоянное время отклика от вашего приложения для каждой протестированной позиции строки и отсутствие сброса или переноса идентификаторов). Кроме того, этот тип активности от злоумышленника создаст много шума, и, вероятно, они будут обнаружены с помощью IDS и IPS.

Это не может полностью предотвратить их, но может усложнить их выполнение атакующим. Было бы намного проще и лучше использовать что-то вроде hash-equals который полностью предотвратил бы атаки по времени, предполагая, что длины строк равны.

Ваш предложенный код

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

Обратите внимание, что PHP rand функция не криптографически безопасна:

Внимание! Эта функция не генерирует криптографически безопасные значения и не должна использоваться в криптографических целях. Если вам нужно криптографически безопасное значение, рассмотрите возможность использования openssl_random_pseudo_bytes() вместо.

Это означает, что теоретически злоумышленник может предсказать, что rand собирался сгенерировать и затем использовать эту информацию, чтобы определить, была ли задержка времени отклика от вашего приложения из-за случайного сна или нет.

Лучший способ приблизиться к безопасности - предположить, что злоумышленник знает ваш исходный код - единственные вещи, которые должны быть секретными для злоумышленника, - это, например, ключи и пароли, - предположить, что они знают используемые алгоритмы и функции. Если вы все еще можете сказать, что ваша система безопасна, даже несмотря на то, что злоумышленник точно знает, как она работает, вы пройдете большую часть пути. Функции как rand обычно устанавливаются на начальное время с текущим временем дня, поэтому злоумышленник может просто убедиться, что его системные часы установлены на том же самом уровне, что и ваш сервер, а затем сделать запросы, чтобы подтвердить, что их генератор соответствует вашему.

Из-за этого лучше избегать небезопасных случайных функций, таких как rand и измените вашу реализацию, чтобы использовать openssl_random_pseudo_bytes который будет непредсказуемым.

Кроме того, согласно комментарию ircmaxell, sleep не достаточно детализирован, поскольку принимает только целое число для представления количества секунд. Если вы собираетесь попробовать этот подход, посмотрите на time_nanosleep со случайным числом наносекунд.

Эти указатели должны помочь защитить вашу реализацию от такого типа временных атак.

Такой подход может полностью предотвратить атаки по времени? Или просто усложнить работу?

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, с (странный процессор для ноутбука среднего класса 2018 года, о котором я никогда не слышал)

и этот цикл:

      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 мегабайт/инт(65126401)

(но как часто вам нужно сравнивать строки размером более 65 МБ с постоянным временем? Я думаю, это не часто)

  • вы можете подумать, что «тогда злоумышленник может отправить ОГРОМНУЮ строку для сравнения и проверить, сколько времени потребуется для создания исключения», но я не думаю, что это сработает, начинается с проверки того, имеют ли обе строки одинаковую длину, и короткого замыкания, если они имеют разную длину, такая атака должна работать только в том случае, если злоумышленник может установить длину для обеих строк достаточно большой для тайм-аута.

  • сегодня у нас есть встроенная функция hash_equals() для сравнения строк одинаковой длины , но hash_equals() не защитит вас от строк разной длины, в отличие от функции выше.

Другие вопросы по тегам