- PVSM.RU - https://www.pvsm.ru -
От переводчика: Хоть посыл статьи Ali Najaf [1], переведённой ниже, и носит слегка рекламный оттенок («оставьте криптографию нам, экспертам»), но описанные в ней примеры показались мне довольно интересными и заслуживающими внимания.
Кроме того, никогда не будет лишним повторить прописную истину: не придумывайте свою крипто-защиту. И эта статья отлично иллюстрирует почему.
Есть четыре стадии компетентности:
Мы все начинаем с первой стадии, нравится нам это или нет. Ключ к переходу от стадии 1 к стадии 2 в любой области — делать много ошибок и получать ответную реакцию. Если вы получаете ответную реакцию, вы начинаете понимать, что вы сделали правильно, что вышло неправильно, и что вам стоит улучшить в следующий раз.
Криптография опасна, потому что вы не получаете ответной реакции, когда делаете что-то неправильно. Для среднего разработчика один блок случайных байт, закодированный в base 64, так же хорош, как любой другой.
Вы можете научиться хорошо программировать непреднамеренно. Если ваш код не компилируется, не делает того, что вам от него требуется, или содержит легко обнаруживаемые баги, вы получаете немедленный отклик. Вы исправляете всё, и в следующий раз у вас уже получится лучше.
Криптографии невозможно научиться непреднамеренно. Если вы не проводите время за чтением материалов об уязвимостях и попытками их использовать, у ваших доморощенных механизмов защиты, основанных на криптографии, не много шансов против реальных атак.
Если вы не заплатите эксперту по безопасности, который знает как взламывать механизмы защиты, основанные на криптографии, вы не сможете узнать, что ваш код небезопасен. Атакующие, обходящие вашу защиту, тоже вам не помогут (в идеале, они смогут обойти её так, что вы никогда об этом не узнаете).
Ниже рассматривается несколько примеров неправильного использования криптографии. Задумайтесь, если бы вы не прочитали этот пост, смогли бы вы обнаружить эти ошибки в реальности?
Один сайт обмена фотографиями как-то аутентифицировал запросы к своему API по следующей схеме:
{ action: create, name: 'my-new-photo' }
).Чтобы убедиться, что запрос действительно пришёл от указанного пользователя, сервер аналогично генерирует подпись для параметров запроса и секрета, который записан у него для этого пользователя.
Код для этого мог бы быть таким:
# НА КЛИЕНТЕ
require 'openssl'
## реквизиты пользователя
user_id = '42'
secret = 'OKniSLvKZFkOhlo16RoTDg0D2v1QSBQvGll1hHflMeO77nWesPW+YiwUBy5a'
## параметры запроса, которые мы хотим отослать
params = { foo: 'bar', bar: 'baz', user_id: user_id }
## считаем MAC
message = params.each.map { |key, value| "key:value" }.join('&')
params[:mac] = OpenSSL::Digest::MD5.hexdigest(secret + message)
## и потом отправляем запрос как-то так...
HTTP.post 'api.example.com/v3', params
# НА СЕРВЕРЕ
## получаем реквизиты пользователя из БД
user = User.find(params[:user_id])
secret = user.secret
## получаем MAC из параметров запроса
challenge_mac = params.delete(:mac)
## вычисляем MAC заново, используя тот же метод, что и клиент
message = params.each.map { |key, value| "key:value" }.join('&')
calculated_mac = OpenSSL::Digest::MD5.hexdigest(secret + message)
## сравниваем значения MAC из запроса и вычисленное заново
if challenge_mac == calculated_mac
# пользователь успешно аутентифицирован - делаем, что он попросил
else
# пользователь не аутентифицирован, ошибка
end
С базовыми знаниями о том, как работает MD5, это совершенно адекватная реализация аутентификации запросов к API. Выглядит вполне безопасно, так ведь? Вы уверены?
Оказывается, такая схема уязвима к так называемой "атаке увеличением длины сообщения" (Length extension attack [2]).
Вкратце:
md5('foo')
, в силу способа вычисления MD5 можно очень легко посчитать значение md5('foobar')
, даже не зная префикса 'foo'.md5('secretfoo:bar')
, можно легко вычислить значение md5('secretfoo:bar&bar:baz')
, даже не зная префикса 'secret'.Любой разработчик, который не знал об этом заранее, запросто бы попался. Разработчики Flickr, Vimeo и Remember the Milk использовали такой подход в своих продуктах [3] (pdf).
Суть не в том, что вы должны знать каждую скрытую мелочь о внутренностях криптографических функций. Суть в том, что есть миллион способов наделать ошибок с криптографией. Так что не трогайте её.
Не убедил? Хорошо, давайте попробуем исправить этот пример и посмотрим, получится ли у нас сделать его безопасным…
Ваш знакомый хакер рассказал вам об этой уязвимости и порекомендовал использовать Hash-based Message Authentication Code (HMAC [4]) для аутентификации запросов.
Отлично! HMAC придуман как раз для нашего случая. Да и замена очень проста, практически один-в-один.
Наш код проверки подписи на сервере теперь может выглядеть так:
require 'openssl'
## получаем реквизиты пользователя из БД
user = User.find(params[:user_id])
secret = user.secret
## получаем HMAC из параметров запроса
challenge_hmac = params.delete(:hmac)
## вычисляем HMAC
## на клиенте мы делаем то же самое, когда генерируем запрос
message = params.each.map { |key, value| "key:value" }.join('&')
calculated_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('md5'), secret, message)
## сравниваем значения HMAC из запроса и вычисленное заново
if challenge_hmac == calculated_hmac
# пользователь успешно аутентифицирован - делаем, что он попросил
else
# пользователь не аутентифицирован, ошибка
end
Выглядит вполне безопасно, так ведь? Вы уверены?
Оказывается, что код выше уязвим к атаке по времени (Timing attack [5]), которая позволяет подобрать правильный HMAC для заданного сообщения.
Вкратце:
Используя описанную выше технику, вы можете достоверно определить HMAC для любого запроса, с которым вы хотели бы вызвать API, и успешно пройти аутентификацию.
Опять-таки, возможно, вы не знали об атаках по времени, но от вас этого и не ожидают. Суть не в том, что вы должны были знать детали специфических уязвимостей и остерегаться их. Суть в том, что есть миллион способов наделать ошибок с криптографией. Так что не трогайте её.
И снова давайте продолжим и попробуем сделать этот код более безопасным…
Вы можете избежать атак по времени, сравнивая присланный и вычисленный HMAC нечувствительным ко времени способом. Т.е. вам нельзя полагаться на встроенный оператор сравнения строк в вашем языке программирования, поскольку он сразу же завершит работу, как только обнаружит первый не совпадающий символ.
Для сравнения строк мы можем воспользоваться тем фактом, что XOR любого байта с самим собой даст 0. Всё, что нам надо сделать — применить операцию XOR к каждой паре соответствующих байтов из строк A и B, сложить получившиеся результаты и вернуть true, если сумма равна 0, и false в противном случае.
На Ruby это могло бы выглядеть как-то так:
require 'openssl'
## функция сравнения строк, нечувствительная ко времени
def secure_equals?(a, b)
return false if a.length != b.length
a.bytes.zip(b.bytes).inject(0) { |sum, (a, b)| sum |= a ^ b } == 0
end
## получаем реквизиты пользователя из БД
user = User.find(params[:user_id])
secret = user.secret
## получаем HMAC из параметров запроса
challenge_hmac = params.delete(:hmac)
## вычисляем HMAC
## на клиенте мы делаем то же самое, когда генерируем запрос
message = params.each.map { |key, value| "key:value" }.join('&')
calculated_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('md5'), secret, message)
## сравниваем значения HMAC из запроса и вычисленное заново
if secure_equals?(challenge_hmac, calculated_hmac)
# пользователь успешно аутентифицирован - делаем, что он попросил
else
# пользователь не аутентифицирован, ошибка
end
Выглядит вполне безопасно, так ведь? Вы уверены?
Я сомневаюсь. Это уже предел моих знаний о потенциальных векторах атак на схемы такого типа. Но я не уверен, что нет способа взломать и это.
Избавьте себя от проблем. Не используйте криптографию. Это плутоний. Есть миллионы способов наделать ошибок и всего несколько ценных способов сделать всё правильно.
P.S. Если вам никак не обойтись без проверки HMAC вручную и у вас есть доступ к модулю activesupport, вы можете осуществлять нечувствительное ко времени сравнение с помощью ActiveSupport::MessageVerifier
. Не пишите его с нуля. И ради всего святого не копируйте мою реализацию выше.
P.P.S. Всё ещё не убеждены? Пройдите Matasano Crypto Challenges [6] — может, хотя бы они изменят ваше мнение. Я не прошёл ещё и половины, и мне уже пришлось связаться с двумя прошлыми клиентами, чтобы исправить у них ошибки с криптографией.
Об авторе: Ali Najaf — технический консультант из Лондона. Занимается разработкой программного обеспечения для стартапов и мелкого и среднего бизнеса. Пишет о технологиях, предпринимательстве и разработке ПО.
Автор: sabio
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/perevody/35508
Ссылки в тексте:
[1] статьи Ali Najaf: http://happybearsoftware.com/you-are-dangerously-bad-at-cryptography.html
[2] Length extension attack: http://en.wikipedia.org/wiki/Length_extension_attack
[3] Разработчики Flickr, Vimeo и Remember the Milk использовали такой подход в своих продуктах: http://netifera.com/research/flickr_api_signature_forgery.pdf
[4] HMAC: http://en.wikipedia.org/wiki/Hmac
[5] Timing attack: http://en.wikipedia.org/wiki/Timing_attack
[6] Matasano Crypto Challenges: http://www.matasano.com/articles/crypto-challenges/
[7] Источник: http://habrahabr.ru/post/181372/
Нажмите здесь для печати.