Pull to refresh

Comments 24

В первом же примере сравнивается токен, также это может быть и идентификатор сессии. Угнать чужую сессию — это, конечно, не пароль узнать, но не менее эффективно в некоторых случаях
Пример мне кажется некорректным. Это немыслимо, чтобы время исполнения запроса на сравнение пароля зависило бы от длины пароля, если конечно длина пароля меньше тысячи символов. Накладные запросы на обработку сетевого запроса, выделение под него динамических объектов в JS, паузы GC, работа других процессов в системе, пападание-непопадание строки в кэш процессора, запрос к БД и степень её разогретости. Есть тысячи факторов от которых зависит время исполнения запроса и для простого пароля его длина, мне кажется, занимает почетное последнее место.
Даже в собеседование иногда включают вопрос, касательно данной уязвимости. Пост от 2017 года кстати.

blog.risingstack.com/node-js-interview-questions-and-answers-2017

What's wrong with the following code snippet?

function checkApiKey (apiKeyFromDb, apiKeyReceived) {
if (apiKeyFromDb === apiKeyReceived) {
return true
}
return false
}
The Solution

When you compare security credentials it is crucial that you don't leak any information, so you have to make sure that you compare them in fixed time. If you fail to do so, your application will be vulnerable to timing attacks.
То что какой-то человек задает такой вопрос на собеседовании не доказывает его корректность. Попробуйте измерить время такого запроса и вы приятно удивитесь. Поймите, что расходы на массу абстракций, которые существуют поверх физической пересылки байт между компьютерами столь высоки, что сравнение 5 или 50 байт пароля просто утонут в этом море.
А вам не кажется, что к оригинальному вопросу ожидался не совсем такой ответ. Возможно там хотели услышать в том числе и про накладные расходы всего стека?
Вам не кажется такой пример как минимум странным, вы выбираете токен из базы, и затем сверяете его с пришедшей строкой (он у вас один)? Любой нормальный человек просто попытается выбрать нужный токен сразу из базы, а это не какая-то константа времени. Если речь идет про пароль, то там не просто сравнение строк, давно уже используют хеширующие проверки а не посимвольное сравнение. Такой вариант будет работать разве что при сравнение захардкоженой строки со строкой из вне, а это уже как минимум странно.

https://jsperf.com/string-comparison-time
Встроенное сравнение строк занимает меньше наносекунды. Даже если допустить, что движок яваскриптовый увидел, что строки константные и заоптимизировал все в ноопы, наивная реализация сравнения через цикл с charAt занимает меньше микросекунды для строки в 60 символов. Что тут можно через сеть намерять?..

В рекламе используют понятие инфоповод. На мой взгляд плохой получился инфоповод, бессмысленный.
Проблема разобьётся о задержки на разных уровнях, которые как мы знаем не константа. Как правильно заметили, нет смысла сравнивать cleartext пароли. Что-то более длинное не даст вам точного понимания, где именно в строке ошибка, в 18 или 24 символе? Вектор есть, но уж очень лабораторные условия должны быть для его реализации.
И тем не менее эта проблема существует с 98-года и по сей день актуальна. Тут лишь приведена часть, касательно node.js
Исследования показывают, что злоумышленнику не составляет труда измерять отрезки времени от 15 до 100 микросекунд через Интернет и 100 наносекунд через локальную сеть

А можно ссылочку на эти исследования?

Что-то мне кажется, здесь минимум на 3 порядка улучшили точность, а речь на самом деле идёт про 15-100 миллисекунд (мс) и 100 микросекунд (0,1 мс)
Просто ради интереса я решил провести замеры.

Ну, если у нас проблема в сравнении строк как концепции — значит этой уязвимости должны быть подвержены все языки, рассудил я. Набросал на коленке PHP-скриптик, запустил на домашнем сервачке (гента на целерончике N3150). Запускал из консоли, PHP 7.0.21 (cli).

Результаты, 100 млн (108) сравнений:
Diff at 5th symbol
42.0196 seconds.
Diff at 15th symbol
41.4606 seconds.

То есть разница есть, целых 0,000 000 005 59 секунды (5,6 наносекунды).

Таким образом, даже если злоумышленник сможет замерять отрезки времени с точностью до 100 наносекунд… это ему ничем не поможет.

Резюмируя: делаем из мухи слона?

P.S. Для ноды я не стал делать тест, но, сомневаюсь, что разница больше чем на 3 порядка.
P.P.S. Почему на PHP первый тест занял больше времени чем второй — тоже весьма интересный вопрос, но в данном случае — оффтопик.
P.P.P.S. Ну и конечно же, как выше и ниже заметили — хэши.
так разница в другую сторону???
UFO just landed and posted this here
А что мешает с текстом сделать то-же самое, что и с паролями?
(Простите что пример на C++, он мне ближе чем JS)

bool isAuthenticated(const char* token, const char* input, int len) {

    int mismatch = 0;

    for (int i = 0; i < len; ++i) {

        mismatch |= token[i] ^ input[i];

    }

    return mismatch;

}
Было бы здорово, если бы в статье разобрали пример эксплуатации именно первого листинга (с "==="). Я пробовал как-то проэксплуатировать "==" в простом сервисе на Flask, запущенном локально. Мне, четно говоря, не удалось отследить разницу во времени.
Чисто умозрительный пример. Хотя и не безынтересный. При сравнении хэшей такая атака невозможна.

Я бы добавил еще один способ защиты. Самый простой и лобовой.
Ставить в конец запроса случайную задержку, например от 1 до 3 мс.

Отличный пример, как взломать приложение, в котором пароли сравниваются прямо в ноде, и к которому имеет доступ только один пользователь. Больше, больше сферических коней в вакууме! И каждый раз помечать их как проблемы в node.js.

Ага, и на любом бэкенде, где серверов больше одного, весь вектор атаки сломается об раунд-робин DNS'а или nginx'а.

Как мы видим – измерение покажет нам, что при установке val0=4 – система ответит нам с небольшой задержкой.
if (p[i] == correct[i]) {
      sleepTime += 0.45;
}
...
sleep(sleepTime * 1000);
Ага, только мы сами ее создали, да еще такую чтоб заметно было.

Мне кажется, практическое применение такого способа может быть только если есть прямой доступ к проверяющей функции, чтобы измерять только ее работу.
И задержки люди пишут (задержки потока для Node.js, вы в своём уме?) и XOR используют. Но если нужно одинаковое время обработки сравнения строк, то надо просто проходить по всей строке до конца. Зачем магия хоров и слипов?
function isAuthenticated(user, token) {
  var userToken = getUserToken(user);
  return notTimingCheckStrings(userToken, token);
}

function notTimingCheckStrings(str1, str2) {
  if(str1.length === str2.length) {
    var result = true;
    for(var i=0; i<str2.lenght; i++) {
      if( str1[i] !== str2[i] ) {
        result = false;
      }
    }

    return result;
  } else {
    //Handle error
    return false;
  }

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

А слипы в коде вставлены исключительно для упрощения атаки, для эмуляции тормозного железа :-)

Весьма интересная статья, спасибо!
Но почему бы было не написать пример с определением mismatch в цикле так, чтобы он исполнялся после копипаста?

Sign up to leave a comment.