К таким данным могут относиться например данные, передаваемые в запросах API, когда критически важна целостность передаваемой информации, или же при передаче данных из Web-форм.
Зачем это нужно?
Если отойти от научной формулировки, что же такое подпись данных и как это реализуется на практике?
Предположим, мы хотим отправить какие-то данные другому человеку, при этом, и нам и получателю важно убедиться, что данные не будут изменены в процессе передачи.
Например, мы имеем исходный массив данных вида:
$data = array(
'param1' => 'value1',
'param2' => 'value2',
'param3' => 'sometext',
'a' => 'b'
);
Самое простое, что мы можем сделать – это каким-то образом сериализовать массив (привести его в строковое представление), добавить в конец получившейся строки некий секретный ключ – набор символов известный только нам и получателю данных (пусть будет “mysecretkey”), после чего применить к этому какую-нибудь хеш-функцию, скажем, md5.
Какие можно встретить практические решения? В зависимости от того, важны ли значения массива или его ключи, можно встретить, например, такие реализации:
$hash = md5(implode(";",array_keys($data)).";"."mysecretkey")) = md5("param1;param2;param3;a;mysecretkey")
или
$hash = md5(implode(";",array_values($data)).";"."mysecretkey") = md5("value1;value2;sometext;b;secretkey")
Какие есть плюсы и недостатки у данных реализаций?
Самый первый и очевидный плюс, но он же и единственный – это простота реализации. Минусы? Их масса, как минимум в качестве ключевых недостатков, можно привести:
- MD5 – уже старый алгоритм, который не считается стойким;
- Если нарушится последовательность параметров или значений – подпись не сойдется;
- Как быть с вложенными (многомерными) массивами?
- Либо ключи, либо значения.
Нужно отметить, что последний недостаток в некоторых случаях, как таковым недостатком и не является – например если мы хотим проверить данные из Web-формы, заполняемой человеком и можем проверить только целостность набора полей, а их значения нам заранее неизвестны.
В частности, таким образом, можно формировать более продвинутые CSRF-токены для форм, используя в качестве секретного ключа какой-нибудь внутренний идентификатор, привязанный к сессии пользователя. Тем самым мы решим сразу две задачи – и защиту от CSRF и контроль целостности набора передаваемых параметров – пользователь уже не сможет «поиграться» с полями формы и попробовать добавить или убрать что-нибудь из параметров.
Остальные три пункта требуют решения. С первым все достаточно просто – используем более современные и стойки хеш-аглоритмы с большей длиной шифрограммы, такие, как SHA256 или SHA512 и спим спокойно.
Второй пункт – тоже решается, если определиться, что элементы массива будут отсортированы по какому-то принципу, скажем, в алфавитном порядке.
Добавлеям перед сериализацией массива:
ksort($data);
в результате чего, получаем массив, отсортированный по ключам в алфавитном порядке, и нам становится не важно, в какой последовательности были переданы переменные в массиве.
Следующая проблема решается уже не так просто. Пока мы работали с плоскими массивами все было достаточно просто – отсортировали, в строчку сложили с каким-нибудь разделителем типа ”;” – и готово. Но как же быть с вложенными (многомерными) массивами?
Во-первых функция ksort не рекурсивная, но это, конечно же не большая проблема и решение было найдено достаточно быстро:
function ksort_recursive(&$array, $sort_flags = SORT_REGULAR)
{
if (!is_array($array)) return false;
ksort($array, $sort_flags);
foreach ($array as &$arr) {
ksort_recursive($arr, $sort_flags);
}
return true;
}
Во-вторых, массив с вложенностями линейно в строчку уже не сложишь – нужно придумывать дополнительные правила (то есть, изобретать велосипед), или использовать уже «настоящую» сериализацию, такую как JSON, который учитывал бы все вложенные структуры. Использование JSON также решает и четвертую проблему, так как мы сериализуем сразу весь массив, не ограничиваясь отдельно его ключами или значениями.
Почему именно JSON а не простой serialize PHP? Выбор в пользу JSON упал не случайно, поскольку это очень популярный формат сериализации, с которым будет легко работать не только в PHP, но и в любых других популярных языках программирования, таких как Java. Наша реализация должна быть предельно легко переносима на другие платформы и с использованием JSON-сериализации это будет сделать проще всего.
В этом случае все перечисленные проблемы решаются, но встает вопрос с секретным ключом – делать простую конкатенацию ключа справа конечно можно, но это не очень эстетично, благо в PHP есть реализация HMAC с выбором произвольной хэш-фукции:
hash_hmac(“sha256”,$data,”mysecretkey”);
HMAC реализует дополнительное XOR данных с ключом и оборачивает сверху указанной хеш-функцией. Сам алгоритм внутри HMAC подробно описан в литературе по криптографии, или в википедии и описывать здесь, каким именно образом происходит шифрование данных на ключе мы не будем. Просто будем пользоваться этим стандартным аглоритмом.
Сводя все изложенное вместе, разработаем простой класс, который реализовывал бы все описанные действия для получения подписи с многомерного массива, вне зависимости от того, в какой последовательности находятся ключи внутри массива.
Итак, получился следующий незамысловатый код:
// определим коды ошибок, которые мы будем возвращать.
define("E_UNSUPPORTED_HASH_ALGO",-1);
class HMAC_Generator{
private $key, $algo;
private $sign_param_name = "hmac";
function __construct($key, $algo = "sha256"){
$this->key = $key;
$this->algo = $algo;
}
function make_data_hmac($data, $key = NULL){
// если не задан ключ в параметре - используем из свойств
if(empty($key)) $key = $this->key;
// если параметр с подписью есть в массиве - уберем.
if(isset($data[$this->sign_param_name])) unset($data[$this->sign_param_name]);
// отсортируем по ключам в алфавитном порядке -
// на случай, если последовательность полей изменилась
// например, если данные передавались GET- или POST-запросом.
HMAC_Generator::ksort_recursive($data);
// сформируем JSON (или другую сериализацию - можно переопределить метрд encode_string)
$data_enc = $this->serialize_array($data);
// формируем и возвращаем подпись
return $this->make_signature($data_enc, $key);
}
function check_data_hmac($data, $key = NULL, $sign_param_name = NULL){
// если не задан ключ в аргументах - используем из свойств
if(empty($key)) $key = $this->key;
// если не задано имя параметра с подписью аргументах в параметре - используем из свойств
if(empty($sign_param_name)) $sign_param_name = $this->sign_param_name;
// если в данных нет подписи - сразу вернем false
if(empty($data[$sign_param_name])) return false;
// исходный HMAC нам приходит в том же массиве, что и данные,
// заберем его значение для сверки и выкинем из массива
$hmac = $data[$sign_param_name];
unset($data[$sign_param_name]);
// сформируем контрольный HMAC
$orig_hmap = $this->make_data_hmac($data, $key);
// проверку осуществляем регистронезависимо
if(strtolower($orig_hmap) != strtolower($hmac)) return false;
else return true;
}
// Установка алгоритма хеширования
function set_hash_algo($algo){
// приведем к нижнему регистру
$algo = strtolower($algo);
// проверим, поддерживается ли системой выбранный алгоритм
if(in_array($algo, hash_algos()))
$this->algo = $algo;
else return
E_UNSUPPORTED_HASH_ALGO;
}
//
// сериализацию и хеширование - выносим в отдельные методы, просто перепишите или переопределите их
//
private function serialize_array($data){
// кодируем все в json, в случае если мы будем собирать подпись не только в PHP,
// такой тип сериализации - оптимальный
$data_enc = json_encode($data, JSON_UNESCAPED_UNICODE);
return $data_enc;
}
// переопределите, если будет другой алго формирования подписи, не HASH HMAC
private function make_signature($data_enc, $key){
// сформируем подпись HMAC при помощи выбранного аглоритма
$hmac = hash_hmac($this->algo, $data_enc, $key);
return $hmac;
}
// статический метод для рекурсивной сортировки массива по именам ключей
public static function ksort_recursive(&$array, $sort_flags = SORT_REGULAR) {
// если это не массив - сразу вернем false
if (!is_array($array)) return false;
ksort($array, $sort_flags);
foreach ($array as &$arr) {
HMAC_Generator::ksort_recursive($arr, $sort_flags);
}
return true;
}
}
Вкратце разберем функциональность класса. В свойствах класса объявлены две приватные переменные для ключа и алгоритма, а также переменная $sign_param_name, в которой содержится имя параметра с подписью (по-умолчанию равно “hmac”), который будет использоваться при проверке данных методом check_data_hmac по-умолчанию.
В конструктор передается один обязательный параметр – это секретный ключ. По-умолчанию выбран алгоритм хеш-функции sha256. Можно переопределить алгоритм, передав его вторым параметром в конструктор. В случае, если переданный алгоритм не поддерживается системой, вернется значение константы E_UNSUPPORTED_HASH_ALGO (то есть -1).
Для создания подписи предусмотрен метод:
make_data_hmac($data, [$key])
С ним все довольно просто – обязательный аргумент это данные, можно также использовать для формирования подписи другой секретный ключ, передав его вторым параметром.
Для проверки ранее созданной подписи мы реализовали метод
check_data_hmac($data, [$key], [$sign_param_name])
Метод принимает аргументы:
- $data – массив с данными;
- $key – необязательный аргумент, в котором можно передать секретный ключ. Иначе будет использован ключ из свойств объекта;
- $sign_param_name – имя элемента массива, содержащего контрольную подпись.
Сама подпись при этом должна быть внутри $data в параметре с ключом $sign_param_name. Если последний не передан – то будет использовано имя из свойства объекта $this->sign_param_name.
В остальном логика очень проста – собираем подпись, сравниваем регистронезависимо полученную подпись с подписью, переданной в данных.
Метод set_hash_algo, позволяет поменять алгоритм хеш-функции после создания экземпляра объекта. Функция рекурсивной сортировки массива реализована в качестве статического метода, чтобы ее можно было использовать вне экземпляра объекта где-то еще.
Примеры
Проиллюстрируем работу класса на простом примере:
// данные будут приведены к последовательности param1, param2, param3 в результате работы ksort
$data = array(
'param3' => 'sometext',
'param1' => 'value1',
'param2' => 'value2',
);
// разные алгоритмы, по-умолчанию - SHA256
$hmac_generator = new HMAC_Generator("myprivatekey");
$hmac_generator_md5 = new HMAC_Generator("myprivatekey","md5");
$hmac_generator_sha1 = new HMAC_Generator("myprivatekey","sha1");
echo "SHA256: ".$hmac_generator->make_data_hmac($data)."\n";
echo "MD5: ".$hmac_generator_md5->make_data_hmac($data)."\n";
echo "SHA1: ".$hmac_generator_sha1->make_data_hmac($data)."\n";
На выходе получим:
SHA256: 7f0a656e00d3a17ab0d04170dfcb4583b4e29e184b9a24d7fed869979d0bf7e8
MD5: 4f91a268c5a8fc4eaa19d7d7cf329583
SHA1: 8c4a7288be7a76fa2c1bd7d481718d1c49d6bca0
Вместо заключения
Мы получили простую реализацию, позволяющую нам подписывать любые данные и проверять переданные подписанные данные. Теперь вы можете подписывать данные, передаваемые через HTTP/REST API, или же создавать продвинутые CSRF-токены для форм и быть уверенными в том, что получаемые данные оригинальны и консистентны.
Все исходные коды доступны в репозитории на GitHub: github.com/idsolutions/HMAC_generator
P.S. Можете форкать и дорабатывать класс на свое усмотрение, комментарии и предложения приветствуются.
Комментарии (101)
IvanIDSolutions Автор
11.07.2015 23:05Ну хорошо :)))
По пункту номер 1:
а) сбрутили идентификатор — начали гнать запросы.
б и ц) будет работать, и будет достаточно секурно, если это сервер->сервер API взаимодействие.
По пункту номер 2:
Сценарий атаки:
Есть интернет-магазин «Ромашка» и банк «Рога и Копыта», который оказывает услуги интернет-эквайринга для Ромашки. На странице чекаута на сайте Ромашки есть форма с hidden-полями: merchant_id, order_id, amount, description. Форма сабмитится на URL банка. Лжепокупатель может изменить order_id или amount и оплатить совсем другой счет и более того — на другую сумму.
Способ защиты:
Передавать еще и HMAC, проверка которого на стороне сервера не даст пользователю изменить данные в форме.lair
11.07.2015 23:12сбрутили идентификатор
Сбрутить GUID? Вы серьезно? А что в этом случае мешает сбрутить секретный ключ для HMAC?
будет работать, и будет достаточно секурно, если это сервер->сервер API взаимодействие.
А если не сервер-сервер, то в чем отличие?
Более того, почему вы считаете, что «сбрутить идентфикатор» можно, а «сбрутить идентификатор и пароль» нельзя?
На странице чекаута на сайте Ромашки есть форма с hidden-полями: merchant_id, order_id, amount, description. Форма сабмитится на URL банка. Лжепокупатель может изменить order_id или amount и оплатить совсем другой счет и более того — на другую сумму.
(а) кто является атакующим — пользователь или третья сторона? Если третья сторона, то каким образом она меняет данные?
(б) зачем эта форма вообще показывается пользователю?IvanIDSolutions Автор
12.07.2015 00:01+1Сбрутить GUID?
См выше про длину инентификатора.
(а) кто является атакующим — пользователь или третья сторона? Если третья сторона, то каким образом она меняет данные?
(б) зачем эта форма вообще показывается пользователю?
мне кажется, что я уже не раз ответил на все эти вопросы и мы идем на очередной круг.lair
12.07.2015 00:04См выше про длину инентификатора.
Где «выше»?
мне кажется, что я уже не раз ответил на все эти вопросы и мы идем на очередной круг.
Нет, вы нигде не отвечали на эти вопросы.IvanIDSolutions Автор
12.07.2015 10:40Читайте внимательно.
lair
12.07.2015 11:09А если клиент — это форма в браузере пользователя, который может быть зловредным и изменит, скажем, сумму покупки, или идентификатор заказа, или еще какие-то данные, то без HMAC не обойтись
Вы вот это имеете в виду?
lair
11.07.2015 23:35В остальном логика очень проста – собираем подпись, сравниваем регистронезависимо полученную подпись с подписью, переданной в данных.
habrahabr.ru/post/181372, раздел «Аутентификация сообщений с помощью HMAC». Ну или security.stackexchange.com/a/74552.IvanIDSolutions Автор
12.07.2015 00:01Соль+алго с большей длиной хеша.
lair
12.07.2015 00:05Соль вообще никак не влияет. А длина хэша… ну что ж, вы увеличили время подбора, да. Но вы не закрыли уязвимость.
IvanIDSolutions Автор
12.07.2015 00:16Чтобы подобрать HMAC sha256 при помощи Timing attack придется отправить огромное количество запросов. Любая нормальная система заблокирует вас намного раньше чем вы успеете первые два символа подобрать :) Но это уже выходит за рамки текущего материала.
lair
12.07.2015 00:20Система, описанная в вашем посте — заблокирует?
IvanIDSolutions Автор
12.07.2015 00:22Обработка лимитов и алерты — это отдельная подсистема, не имеющая отношения к криптографии.
Правильно построенная система — да, заблокирует, если еще раньше не заблокирует WAF, не ограничит nginx по rate limit и т.п.lair
12.07.2015 00:24Вот поэтому решение, которое вы описываете в посте — уязвимо (и, собственно, является наглядной демонстрацией описанного по первой моей ссылке).
И при этом вы до сих пор не смогли показать, в каких случаях от него есть выгода.IvanIDSolutions Автор
12.07.2015 00:27Уязвимо в каком случае? Если можно безконтрольно брутить? Какое же решение в таком случае неуязвимо?
lair
12.07.2015 00:34Уязвимо в каком случае? Если можно безконтрольно брутить? Какое же решение в таком случае неуязвимо?
Неуязвимых решений не бывает, бывают решения, которые уязвимы менее и более. Я вам указал на очевидную (и, в общем-то, сравнительно легко устранимую) уязвимость вашего. Просто что бы вы понимали: стоимость брутфорса вашего решения — k*n, решения, неподверженного тайминг-атаке — kn (где k — это временные затраты на перебор всех значений в одной позиции хэша, а n — количество позиций в хэше).IvanIDSolutions Автор
12.07.2015 11:18В теории — ОК. Но только я не понимаю, чем это решение более уязвимо, чем предложенные вами выше решение с "(а) просто идентификатором (б) идентификатором и паролем (ц) идентификатором и хэшом от пароля + nonce"
lair
12.07.2015 11:19Ничем. Вопрос в том, зачем оно нужно вообще (в смысле, от какой атаки защищает).
okazymyrov
20.07.2015 09:33+1Для рассматриваемого случая необходимо выполнить минимум 1024*256*256= 226 запросов. Пусть один запрос равен 1 КБ, тогда вам необходимо передать 65 ГБ информации к серверу (столько же на получение), чтобы вычислить HMAC для одного фиксированного сообщения на основе атаки по времени.
Вы можете сами расчитать вероятность того, что ни однин слой безопасности вас не отфильтрует (примеры). К тому же в нормальных приложениях используется метка времени, что даёт дополнительную защищаету от данной атаки.lair
20.07.2015 09:38Откуда вы взяли 1024*256*256?
Метка времени — это хорошо, но в данном случае ее нет.
(Я правильно понимаю, что вы считаете тайминг-атаку пренебрежимой опасностью?)okazymyrov
20.07.2015 09:49+1Опять же возвращаемся к статье:
Шум от задержек можно сглаживать двумя способами: ...
Предположения:
- 1024 — количество запросов;
- 256 — количество символов в байте;
- 256 — количество байт в хэш-значении.
Метка времени — это хорошо, но в данном случае ее нет.
Автор описывает метод аутентификации данных (с заголовком статьи я не согласен), а не их хранение или формат. Поэтому комментарий не уместен.
(Я правильно понимаю, что вы считаете тайминг-атаку пренебрежимой опасностью?)
Я считаю атаку по времени ещё одним видом атаки, от которой существуют свои методы защиты.lair
20.07.2015 11:18Автор описывает метод аутентификации данных (с заголовком статьи я не согласен), а не их хранение или формат. Поэтому комментарий не уместен.
Как-то нелогично.
Если описывается метод, то это одно (и надо оговаривать его ограничения).
Если описывается реализация, то это другое (и надо учиться видеть ее ошибки).
Наконец, если описывается система, то это третье.
Я считаю атаку по времени ещё одним видом атаки, от которой существуют свои методы защиты.
Из которых самый простой — это просто закрыть дырку (не отдавать зависимое от времеи сравнение).okazymyrov
20.07.2015 11:26+1Приведу цитату из всё той же статьи:
Выглядит вполне безопасно, так ведь? Вы уверены?
Я сомневаюсь. Это уже предел моих знаний о потенциальных векторах атак на схемы такого типа. Но я не уверен, что нет способа взломать и это.
Из которых самый простой — это просто закрыть дырку (не отдавать зависимое от времеи сравнение).
Вы знаете более безопасный метод, реализующий тот же самы функционал, чем описано в статье?lair
20.07.2015 11:33Вы имеете в виду — более безопасный, чем HMAC, или более безопасный, чем та реализация HMAC, которая описана в посте?
okazymyrov
20.07.2015 12:46+1Чем реализация в статье плоха, с точки зрения ИБ, если не принемать во внимание атаку по времени?
lair
20.07.2015 13:20+1(Ордеринг ключей уже указали)
Я других проблем не вижу, но это не означает автоматически, что их нет. У меня изначально больше претензий было к концепции (области применения), чем к реализации.
MaximChistov
11.07.2015 23:54Любые системы безопасности, основанные на том что взломщик не может никак узнать общий ключ «просто потому что» — уязвимы.
MaximChistov
12.07.2015 00:21+1Единственное применение — server-to-server over https, стоит по-моему упомянуть в статье что это не клиентская методика
AlexGx
12.07.2015 00:25HMAC много где используется (многие API многих сервисов, включая api вонтакте), ключ знает только сервис и апи-клиент, слово общий здесь некорректно. Почитайте как это сделано у амазона docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/HMACAuth.html
пока писал коммент, вы написали еще один — да, это совсем не для браузерного клиентсайда.
vanxant
12.07.2015 00:32Первое правило криптографии — нельзя изобретать велосипеды в области криптографии.
Если у вас HTTPS и взаимодействие сервер-сервер, то, censored, почему вы не используете https с двусторонней проверкой сертификатов?
Пусть даже самоподписанных.
Для банк-клиента в «сферическом банке в вакууме» нужно не просто сгенерировать личный ключ и предоставить его банку, нужно, блин, распечатать его в шестнадцатеричном виде на бумаге в двух экземплярах, подписать, поставить печать и принести в банк лично — и только после этого банк-клиент заработает. Почему для того же самого, по сути, банк-клиента, только в виде модуля для CMS (то есть полностью автоматизированного и работающего в заведомо контролируемом третьей стороной (хостером) окружении), вы делаете кривой велосипед?
Потому что так принято «у больших», которые до сих пор пишут онл@йн через собаку?IvanIDSolutions Автор
12.07.2015 10:31Не могу говорить за всех, но HMAC тем не менее используется почти во всех API банков и платежных систем, которые мне доводилось встречать, а подключал я их очень много. Лишь у одного была проверка клиентского сертификата. И хоть убейте, я не считаю это решение практически уязвимым, брут хеша — это очень теоретическая уязвимость. На практике, после 10 неудачных попыток, любая нормальная система Ваш ip заблокирует, а инцидентом будет заниматься служба ИБ.
Disasm
12.07.2015 02:02Было бы намного логичнее согласовывать данные платежа с банком напрямую, а в форму вставлять только какой-нибудь идентификатор транзакции. К тому же вам, по сути, нужно подписать данные, которые уходят только в одном направлении, а для этого можно использовать цифровую подпись, а не MAC. Плюсы очевидны: одна точка отказа вместо нескольких. А вот для подписи cookies такой MAC отлично бы подошёл.
IvanIDSolutions Автор
12.07.2015 11:21Такие решения тоже бывают. Я вообще считаю что все взаимодействие должно происходить сервер-сервер, а пользователю должен выдаваться URL для перенаправления. Но существуют разные решения, и решение с формой и HMAC весьма популярно и много где встречается.
lair
12.07.2015 11:31А форма при этом обязательно размещена на HTTPS?
IvanIDSolutions Автор
12.07.2015 11:31См выше — HTTPS мы подразумеваем по-умолчанию.
lair
12.07.2015 11:32Вы-то — да (и именно поэтому такое количество вопросов возникает). А банки, которые интегрируются описанным способом?
IvanIDSolutions Автор
12.07.2015 11:34Да, во всех таких реализациях, которые я видел, форма сабмитится на банковский URL всегда по HTTPS.
lair
12.07.2015 11:35+1Вопрос не только в том, куда форма сабмитится, но и в том (даже больше в том), как она доставляется клиенту.
IvanIDSolutions Автор
12.07.2015 11:40Ну если учитывать, что клиентская сторона это в большинстве случаев страница чекаута в интернет-магазине, там скорее всего обычный HTTP, но я все же немного не улавливаю суть вопроса, на что это влияет, если форма в любом случае доступна пользователю браузера.
lair
12.07.2015 11:44+1Это влияет на количество потенциальных атакующих сторон.
Если у вас страница чекаута доставляется по HTTP (буууу), то ее можно как угодно подменить в полете. А дальше мы прекрасно имеем атаку класса «возьми payload+HMAC от нужного нам заказа, сформируй страницу подтверждения с данными заказа жертвы, отправь на оплату нужного нам заказа». И как HMAC от этого защитит?
Я же не зря у вас спрашиваю, кто является атакующей стороной. Когда у вас HTTPS, их (сторон), условно, три: пользователь, браузер (зловредный или скомпроментированный), третья сторона с помощью уязвимости сайта (например CSRF). Когда у вас HTTP, сторон немедленно становится на одну больше.IvanIDSolutions Автор
12.07.2015 11:48Теперь я понял о чем вы. Безусловно, это так. И именно поэтому в наших решениях HTTPS используется абсолютно везде по-умолчанию, а HMAC/CSRF-токены (+ лимиты соединений) реализуют дополнительный слой для аутентификации и контроля логической целостности сообщений.
lair
12.07.2015 11:50+1Я же не ленивый, я еще раз спрошу: от какой конкретно атаки и от кого вас защищает HMAC? (CSRF из рассмотрения исключаем, там HMAC — это всего лишь один из вариантов построения защиты, и не самый сильный)
IvanIDSolutions Автор
12.07.2015 11:52Я тоже не ленивый, еще раз отвечу — от изменения пользователем данных формы. Разумеется, при условии, что контролируются лимиты.
lair
12.07.2015 11:58+1То есть злоумышленник — пользователь. А сценарий у нас, насколько я помню, магазин и платежная система, между магазином и платежной системой нет предварительного обмена (т.е. платежная система принимает решение о том, что и куда оплачивать, на основании данных, приходящих с формы).
Вот вы доставили пользователю форму, в которой есть payload (идентификатор мерчанта, идентификатор заказа, сумма). Как он дальше может провести атаку? (я специально убрал HMAC, чтобы сделать возможной любую атаку подменой)IvanIDSolutions Автор
12.07.2015 12:02Вариантов масса, в зависимости от бизнес-логики интернет-магазина при обработке Result от платежной системы. Если интернет-магазин проверяет на Result только ID заказа и не проверяет сумму — вектор очевиден.
lair
12.07.2015 12:06+1Ну так очевидно же, что проверять только идентификатор заказа — заведомая ошибка, и даже не только с точки зрения безопасности, но и с точки зрения бизнес-логики (платежные системы тоже ошибаются).
IvanIDSolutions Автор
12.07.2015 12:07Нам с вами очевидно — мерчанту далеко не всегда. Я сам лично неоднократно встречал такие векторы и успешно их эксплуатировал.
lair
12.07.2015 12:10В этом случае нужно исправлять эту ошибку, а не на форме добавлять контроль. Еще варианты?
IvanIDSolutions Автор
12.07.2015 12:12Контроль на форме, навязанный платежной системой, как минимум, может защитить клиента-мерчанта от таких детских векторов, которым они, увы, зачастую подвержены.
lair
12.07.2015 12:28Платежная система может навязать и проверку суммы платежа, вообще-то.
Но, как видите, проблема, которую вы в данном случае пытаетесь решить — не технологическая, а организационная.
lair
12.07.2015 12:33… и да, если бы платежная система требовала предварительной передачи всех данных отдельной операцией, этой проблемы вообще бы не возникло. Это если со стороны платежной системы смотреть.
VolCh
12.07.2015 12:48Предположим, мы хотим отправить какие-то данные другому человеку, при этом, и нам и получателю важно убедиться, что данные не будут изменены в процессе передачи.
Очень странное предположение. Вроде описана задача контроля целостности данных, а решение приводится для задач, традиционно решаемых с помощью механизмов ЭЦП, которые решают несколько больше задач, чем просто контроль целостности, а именно:
— невозможность отказа от авторства, с одной стороны
— доказательство авторства, с другой.
При этом описан механизм, похожий на ЭЦП с использованием известного обеим сторонам ключа, который эти дополнительные задачи не решает:
— автор сообщения не может доказать, что он сообщения не писал, если ему предоставляют сообщение, подписанное общим ключом
— автор сообщения не может доказать что сообщение писал именно он, поскольку ключ известен обеим сторонам.
По сути решена задача контроля целостности архива сообщений, типа удостоверения что с этими сообщениями «я» ознакомлен и они с тех пор не изменялись, где «я» включает отправителя и получателя без разделения, поскольку нет секретной информации известной только получателю или только отправителю.
okazymyrov
20.07.2015 08:44Да, действительно в теории так и есть. Но что же происходит в реальной жизни?
Оказывается, что банк является и сервером и центром сертификации в одном лице. Это приводит к тому, что, на практике, нет разницы система с открытым клюм или симметричная с меткой времени, так как секретный ключ пользователя известен банку при генерации пары открытый/секретный ключ.
vsb
12.07.2015 18:06HMAC хорош главным образом своей скоростью. Вся криптография на открытых-закрытых ключах это довольно медленно, в сравнение с быстрым SHA2-хешем.
Ещё хорошее применение для HMAC — хранение сессионных данных в cookie, например id залогиненного пользователя. Конечно эти данные не должны быть секретными. Из плюсов по сравнению с серверной сессией: проще масштабировать.
volovikov
12.07.2015 18:54JSON нельзя использовать для сериализации в подобных целях — он не гарантирует порядок сортировки полей в объекте (http://json.org/) Всё зависит от реализации библиотеки для конкретного языка.
IvanIDSolutions Автор
12.07.2015 19:25Прочитайте статью внимательнее, там специально делается рекурсивная сортировка массива по ключам перед сериализацией в JSON и функция для этого написана.
volovikov
12.07.2015 19:42+1Я прочитал достаточно внимательно. Вы пишете:
Почему именно JSON а не простой serialize PHP? Выбор в пользу JSON упал не случайно, поскольку это очень популярный формат сериализации, с которым будет легко работать не только в PHP, но и в любых других популярных языках программирования, таких как Java. Наша реализация должна быть предельно легко переносима на другие платформы и с использованием JSON-сериализации это будет сделать проще всего.
То что вы сортируете массив по ключам перед кодировкой его в JSON, ровно никак не гарантирует вам то что в JSON-е они буду иметь тотже порядок, вот выдержка из спецификации:
An object is an unordered set of name/value pairs.
Другими словами, hash полученный Вами в PHP и, например, в Java теоретически может отличаться, несмотря на одинаковую сортировку ключей перед кодированием в JSON.IvanIDSolutions Автор
12.07.2015 20:02Хм, интересно, на практике все работало нормально, нужно будет провести больше тестов.
volovikov
12.07.2015 20:31Простой пример, в последний версиях Chrome и FF
JSON.stringify({a: 'a', 1: 1}); {"1":1,"a":"a"}
в PHP
echo json_encode(['a' => 'a', 1 => 1]); {"a":"a","1":1}
lair
Какую именно задачу вы пытаетесь тут решить?
Подписывать-то можно, но как удостовериться, что данные пришли именно от ожидаемого отправителя? И чем это лучше банального HTTPS?
IvanIDSolutions Автор
Задачу валидации входящих данных из Web-формы (убедиться что набор переданных полей соответствует изначальному набору формы и не изменен пользователем), а также защиту формы от CSRF.
Если секретный ключ знают только отправитель и получатель данных — по подписи и удостовериться. Вокруг этого собственно все и крутится.
HTTPS шифрует данные, чтобы они не могли быть прочитаны. В случае если реализовать проверку сертификата клиента — то да, можно проверять отправителя — но это сложный способ и непрактичный, если например клиенты обращаются к API, скажем платежного шлюза и посылают запросы, целостность которых должна быть гарантирована.
lair
А зачем вообще передавать эти данные через форму тогда, если пользователь не может их изменить?
Каким образом?
Это если отправитель один. А если их много?
IvanIDSolutions Автор
Мы говорим о наборе полей, а не самих данных. В форме есть набор полей — зачем пользователю менять сам набор, если он должен заполнить только значения?
Читайте статью: «В частности, таким образом, можно формировать более продвинутые CSRF-токены для форм, используя в качестве секретного ключа какой-нибудь внутренний идентификатор, привязанный к сессии пользователя.» — привязываем «секрет» к сессии и получаем автоматически CSRF-токен.
Они передают в параметрах свой ID, ну а система получателя знает секретные ключи для каждого ID и вполне так себе проводит аутентификацию.
lair
А еще гарантирует, что сообщение не было изменено в пути.
Чем он сложнее, чем HMAC, требующий pre-shared secret?
Что непрактичного в использовании HTTPS для гарантии целостности запросов?
IvanIDSolutions Автор
HTTPS мы подразумеваем по-умолчанию. HMAC не ставится в противовес или замену HTTPS, а как дополнительный механизм аутентификации и проверки консистентности данных на логическом, а не на транспортном уровне. Улавливаете разницу?
lair
Какой выигрыш вы получаете, используя HMAC в дополнение к HTTPS?
IvanIDSolutions Автор
Защита логики в формах — раз.
Аутентификация и защита от изменения пользователем запросов в API, в частности, в платежных формах и платежных API — два.
lair
Можете наглядно показать на примерах? Я вас не понимаю иначе.
А то ведь для аутентификации в API достаточно просто передать свой идентификатор, зачем HMAC-то поверх городить?
IvanIDSolutions Автор
Есть сервер платежной системы, он принимает запросы на транзакции от клиентов, которых много. Передавая просто идентификтор — мы не можем клиента аутентифицировать. Это первый случай.
Второй случай — часто платежная форма инициализируется в виде GET или POST запроса, который происходит на стороне клиента (сабмитится форма в браузере) и в этом случае сервер получающей стороны должен быть уверен что данные сформированы легитимным клиентом и не были изменены. Так понятнее?
lair
Почему?
Данные сформированы легитимным клиентом — это та же самая аутентификация, см. выше.
Какие данные не были изменены и с какого момента?
IvanIDSolutions Автор
Если клиент — это доверенная серверная часть и идентификатор такой, что его нельзя подобрать или угадать — то теоретически можно сделать так, как вы говорите. А если клиент — это форма в браузере пользователя, который может быть зловредным и изменит, скажем, сумму покупки, или идентификатор заказа, или еще какие-то данные, то без HMAC не обойтись и один лишь идентификатор передавать недостаточно.
lair
Давайте мы не будем путать две разные вещи, а?
Еще раз, по порядку.
(1) в чем выигрыш от использования HMAC (по сравнению с (а) просто идентификатором (б) идентификатором и паролем (ц) идентификатором и хэшом от пароля + nonce) при аутентификации в API при работе поверх HTTPS?
(2) пожалуйста, опишите пошагово сценарий атаки, от которого вы пытаетесь защититься с помощью использования HMAC по форме (и сам способ защиты).
okazymyrov
(1) Вы же сами дали ответ на свой вопрос в своём комментарии. В абзаце описании атаки с увеличением длины сообщения вы найдёте ответы, если рассматривать схемы построеные с применением классической схемы Меркла-Дамгарда, коим и является SHA-2.
lair
А причем тут sha-2? Мой вопрос был про работу поверх https.
okazymyrov
Вопрос начинался с
При тестировании веб-приложений в финансовом секторе (о котором не раз упоминал автор) предпалагается, что HTTPS соединение может быть прослушано. Далее ответ на ваш вопрос в предыдущем комментарии.
lair
Эээ, кем предполагается?
Ну и да, если https взломан, то понятно, что (а) идентификация плейнтекстом не работает и (б) вообще можно копать могилку, потому что в описываемых автором платежных сценариях по этому https дальше полетят данные моей кредитной карты.
okazymyrov
Теми, кто тестирует эти системы. :)
Автор лишь описывает метод, позволяющий проверить целостность данных, а не их конфеденциальность. И его метод является более защищённым, с точки зрения криптографии, чем просто хэшифрование с секретным значением (и случайными данными). Но и у него есть свои недостатки.
lair
Понимаете, в ряде контекстов такое предположение автоматически делает систему незащищенной (в частности, я тут разрабатываю маленький проектик, так вот там утечка пользовательских данных опасна сама по себе).
Вопрос в области применимости этого метода. Я вот представлю себе две (скорее всего, это не исключительно, но близко): обмен по незащищенным каналам (например, есть hawk, http-аутентификация на основе HMAC) и контроль полномочий третьей стороны без хранения состояния.
Я любопытства ради разверну второй пункт, просто ради демонстрации хода мысли. Условия: есть система А, это api, в нем есть аутентификация и список ресурсов, доступных после аутентификации. Есть система Б, которая относится к тому же решению, но частично физически отделена. Она отдает собственно тела ресурсов (Грубо говоря, Фейсбук-вебсайт и Фейсбук-CDN-с-картинками). Есть клиент (приложение или третий веб-сайт), который идет в api, получает список ресурсов, потом идет в хранилище и достает тела ресурсов на основании адресов, полученных из api. Права клиента со временем могут меняться (т.е., ранее доступный ему ресурс может быть отозван).
Решение (1): заставляем клиент так же аутентифицироваться в хранилище, потом проверяем доступность. Достоинства: самый безопасный подход. контроль доступа настолько полный, насколько это вообще может сделать api. Недостатки: у клиента усложняется процесс обмена (особенно если это браузер и картинки), увеличивается нагрузка на подсистему аутентификации и авторизации.
Решение (2): вместе с каждым адресом ресурса отдаем короткоживущий идентификатор, по нему хранилище проверяет права доступа. Достоинства: клиентский процесс радикально упрощается. Недостатки: идентификатор короткоживущий, а не одноразовый, поэтому отъем прав происходит не мгновенно (точнее, можно сделать мгновенно, но для этого в апи надо делать убийство идентификаторов, это будет очень дорого), и хранилищу все еще надо куда-то ходить, чтобы проверять валидность идентификаторов.
Решение (3): вместе с каждым адресом ресурса отдаем таймстамп + HMAC (от таймстампа), хранилище по HMAC проверяет, что таймстамп действительно пришел от api, и отсчитывает время жизни от него. Достоинства: клиентский процесс такой же простой, как в (2), но хранилищу теперь не надо никуда ходить, достаточно знать shared key. Недостатки: задержка при отъеме прав (причем теперь неустранимая, в отличие от (2)) и… а вроде бы и все. Да?
okazymyrov
Оффтоп. Вы уже так много материала написали по поводу данной статьи, что можно все комментарии собрать и оформить в виде отдельной статьи со своим видением ситуации.
Про (2) («пожалуйста, опишите пошагово сценарий атаки...») я вообще ничего не писал — этот вопрос относится к автору. Я согласен, что чёткого описания модели угроз в статье нет. Но с другой стороны, это не научная статья, а лишь описание реализации одного из методов (на сколько я это вижу).
А вообще, автор рассматривает один из методов (причём не самый плохой) обеспечения целостности данных, а не универсальный методо защиты от всех существующих и потенциальных атак. Он ничего не упоминает об обеспечении конфеденциальности или неотказуемости (это к ЕЦП).
lair
Я не вижу, что я могу сказать нового аудитории — все мои знания так или иначе почерпнуты из литературы и интернетиков.
Я у вас, вроде, ничего про это и не спрашивал…
Вопром в том, какие у этого метода преимущества по сравнению с соседними, которые заодно обеспечивают, например, и конфиденциальность.
okazymyrov
HTTPS сам по себе обеспечивает лишь конфиденциальность данных, иногда ещё и целостность, при передаче браузер<->сервер (или сервер<->сервер). Но о самих данных он ничего не знает (прсто байтики информации). Ему будет всё ровно «sum=10» или «sum=1000». Проверку целостности «sum» и пытается выполнить автор при помощи HMAC.
Или вы говорите про какие-то другие «соседние методы»?
lair
Эээ, я всегда считал, что если у нас установлено https-соединение, то получающая сторона может быть уверена, что получает именно те данные, которые отправила передающая сторона. Я где-то не прав?
Ну, например, можно целиком все сообщение подписывать ЭЦП, или вообще подписывать и шифровать.
okazymyrov
Нет, почему же, Вы правы. Давайте попробую объяснить немного по другому. Конфеденциальность и целостность данных может происходить на уровне браузера (установлено HTTPS соединение) и на уровне пользователя (сервер проверяет, что данные не были изменены до/после/во время HTTPS сессии). Если HTTPS какием-то образом скомпроментирован, то должен оставаться ещё один уровень безопасности.
Хотя даже я слукавил. Основная цель HTTPS — это проверка того, что вы общаетесь с нужным сервером/клиентом (для защиты от атаки человек посередине). А конфеденциальность и целостность это уже побочные услуги, так сказать в дополнение.
Если сделать предположение, что HTTPS отсутствует (или может быть выполнена атака), то общение с сервером всё ровно должно происходить в защищённом режиме. Или должно быть доказано, что при таких условия злоумышленнику понадобится больше времени для компроментации информации, чем время жизни самой информации.
lair
Я уже говорил: я привык рассматривать ситуацию «HTTPS скомпроментирован» как автоматическую компроментацию всего приложения, потому что в моем случае конфиденциальность информации превыше всего.
Вы предлагаете мне строить второй уровень шифрования (именно шифрования) поверх HTTPS?
IvanIDSolutions Автор
Бывает целый ряд задач, когда к доверенному серверу через HTTPS подключается множество недоверенных клиентов, и реализация не предусматривает возможность добавлять авторизацию по клиентским сертификатам. Далеко не ходить за примером — API почти всех платежных систем используют HMAC в том или ином виде.
lair
(Пойдем по кругу?)
Во-первых, я говорю про конфиденциальность данных, а вы — про авторизацию. Это разные вещи, и путать их не стоит.
Во-вторых, есть два разных сценария — мы рассматриваем компроментацию HTTPS, и мы ее не рассматриваем. Если мы ее рассматриваем, то не понятно, зачем там HTTPS изначально, и можно возвращаться к обсуждению «зачем HMAC используется в несекьюрных транспортах». Если мы ее не рассматриваем, то не понятно, какой выигрыш от использования HMAC (и эти сценарии мы уже обсуждали).
okazymyrov
В критичных приложениях, где без этого не обойтись, — да. В некритичных применять хотя бы целостность сообщений.
А вы не сталкивались с больших компаниями, у которых есть свои ЦС, и которые фильтруют любой трафик, включая HTTPS (и это прописано в политике ИБ)?
lair
Я говорю про свой конкретный сценарий. Если у меня нарушена конфиденциальность — я могу накрываться полотенчиком и ползти на кладбище. Поэтому мне нет смысла строить дополнительный контроль целостности сообщений поверх HTTPS.
Сталкивался. Поскольку приложение, над которым я работаю, ориентировано на конкретных конечных пользователей (физиков), никто не ожидает, что они будут его использовать изнутри такой компании. Если же они это делают с компьютера, за которым у них нет контроля, они делают это на свой страх и риск. Дело в том, что внутри такой компании обычно не только есть фильтрация трафика, но и доменный контроль за компьютерами, а в этом случае защитить пользователя очень сложно (хотя и возможно).
okazymyrov
Я всё же настаиваю, чтобы вы описали Ваш конкретный сценарий в отдельной статье, и объяснили почему проверка целостности данных, при помощи HMAC, лишняя и не имеет смысла.
Конкретно в сфере финансов — такой подход оправдан.
lair
Потому что в моем конкретном сценарии при потере конфиденциальности данных все гарантии сервиса по отношению к пользователю нарушены, и сервис считается неработающим. Это такие входные условия.
okazymyrov
Ну тогда вы уже ответили на вопрос в своём комментарии. Всё зависит от того где и как использовать, и от входных данных конечно же.
lair
Вот мне и интересен круг задач, в которых HMAC применим. Я свои два примера, где я считаю его оправданным, привел.
okazymyrov
Пример сценария с которым я сталкивался в реальной жизни. Есть мобильное устройство, на котором установлено приложение для оказания финансовых услуг. Предполагается, что первая инициализация приложения происходила через доверенную сеть пользователя, в результате которой сгенерирован секретный ключ на стороне клиента и сервера. Доступ к этим ключам получить не представляется возможным. Доступ к самому приложению не возможен из-за пароля, выбранного пользователем, и блокируется после нескольких попыток ввода неправильного пароля. Предполагается, что приложение переодически пересылает 1 цент на фиксированный счёт. Незащищенный паролем мобильный телефон был украден.
Цель злоумышленника перевести деньги на свой счёт и/или изменить количество переводимых денег.
Сценарий атаки без HMAC. Устанавливаем один из прокси-серверов с возможностью MitM, например mitmproxy или OWASP ZAP. Устанавливаем корневой сертификат на телефон. Далее перехватываем запрос, изменяем его. Profit.
Сценарий атаки c HMAC.
Проделывает тоже самое, что и в предыдущем пункте. Только теперь, для того, чтобы изменит сообщение нужно подделать и HMAC, среди данных передаваемого запроса к серверу содержится метка времени. Любая рассинхронизация с сервером более чем на 1 минуту приводит к автоматическому отфильтровыванию сообщения. Хэш (HMAC) берётся от всех полей заголовка http запроса и данных.
lair
В этом сценарии, конечно, есть уйма сомнительных мест (самое дикое из которых — отсутствие certificate pinning), но я вас услышал.
okazymyrov
В теории действительно более безопасней. А вы пробовали это реализовать на практике? Основная проблема будет в управлении ключами. Необходимо будет создавать и сопроваждать инфраструктуру открытых ключей. У какждого метода есть свои плюсы и минусы. Тот же SRP может быть использован, в некоторых случаях, в качестве замены на мобильных устройствах.
lair
К сожалению, да.
По вашему, сопровождать shared secret keys чем-то легче?
okazymyrov
При симметричной криптографии всё можно засунуть в один сервер. А при безопасной реализайии ИВК, нужно отдельная инфраструктура со своими требованиями. Это не просто взяли сгенерировали кучу самоподписанных сертификатов и раздали их доверенным пользователям.
lair
Почему? Это же зависит от конкретной задачи.
webportal
Так не надо сумму покупки на клиенте считать и всё) Делайте как все белые люди корзину от клиента максимум ИД товара и его количество. А то что вы предлагаете не имеет смысла в случае с формой т.к. вы браузеру сначала отдаёте функцию шифрования и всё ваша защита становится вашей уязвимостью…