В прошлой статье я рассказал, как использование серверов времени (NTP и NTS) решает проблему манипуляцией временем транзакции в блокчейне Hyperledger Fabric. И к каким финансовым последствиям приводит атака на примере концепта вымышленного уязвимого смарт-контракта, имитирующего цифровой финансовый актив. Концепт-код для защиты от атаки был написан на Go. Поэтому он не применим для смарт-контрактов Hyperledger Fabric, написанных на других языках (Java, JavaScript, TypeScript). Поэтому, я решил сделать Оракул времени: смарт-контракт, который будет источником времени для других смарт-контрактов. Это позволит использовать оракул времени смарт-контрактами, написанными на других языках. Оракул времени доступен в исходном коде.
В этой статье убедимся, что оракул устойчив к атаке "человек посередине" (при использовании NTS). Для атаки будем использовать утилиты netsed (для подмены не зашифрованных данных) и mitmproxy (для подмены сертификата TLS). А также убедимся, что данные протокола NTP подменить возможно. Читатели, которые не знакомы с блокчейном Hyperledger Fabric, могут представить, что оракул времени - это некий API-сервис, возвращающий текущее время (посредник между клиентом и NTP/NTS сервером).

Описание макета

Оракул запущен в докере (сеть докера 172.18.0.0/24). На хосте докера (Host_1) запущены netsed (порт 4000/UDP) и mitmproxy (порт 8080/TCP). Трафик на них от оракула будет попадать через iptables (правила приведу ниже). Со второго хоста (Host_2) происходит обращение к функциям оракула GetTimeNtp и GetTimeNts (через вызов peer chaincode query, что создаёт защищённое через TLS соединение). Обращение к этим функциям приводит к обращению оракула к NTP серверу и NTS серверу соответственно.

Рисунок 1. Макет
Рисунок 1. Макет

Подмена данных NTP-протокола

Начнём с NTP-протокола. Я перехватил данные между оракулом и NTP-сервером (213.234.203.30), запустив tcpdump на хосте с оракулом. Взглянув на данные протокола в Wireshark, я увидел, что 3-жды передаётся дата (поля "Reference Timestamp", "Receive Timestamp", "Transmit Timestamp"). Начало данных в данном случае начиналось с %ea%33.

Рисунок 2. Ответ NTP-сервера в Wireshark
Рисунок 2. Ответ NTP-сервера в Wireshark

Не углубляясь в принцип кодирования временной метки в NTP-протоколе, я подменил %ea%33 на %eF%33. Правило iptables выглядело так:

iptables -t nat -A PREROUTING -s 172.18.0.0/24 -d 213.234.203.30/32 -p udp -m udp --dport 123 -m udp -j REDIRECT --to-ports 4000
Рисунок 3. Правило подмены данных для NTP в netsed
Рисунок 3. Правило подмены данных для NTP в netsed

Как видно из рисунка 3, правило замены сработало 3-жды. Т.е. как раз столько, сколько ранее в Wireshark было временных меток с искомой датой. Итог подмены: при обращении к функции GetTimeNtp время указывает на 2027 год.

Рисунок 4. Результат запроса к функции GetTimeNtp в условиях работы netsed
Рисунок 4. Результат запроса к функции GetTimeNtp в условиях работы netsed

Как и ожидалось, NTP-протокол подвержен атаке "человек посередине".

Подмена данных NTS-протокола

Повторив всё то же самое для NTS-сервера (ntp1.glypnod.com, адрес
104.131.155.175), получилось вот что. netsed отработал также (использовался другой порт 8123, т.к. сервер 104.131.155.175, в процессе NTS соединения, сообщил этот порт).

Рисунок 5. Правило подмены данных для NTS в netsed
Рисунок 5. Правило подмены данных для NTS в netsed

В Wireshark видим, что замена прошла удачно: снова 2027 год.

Рисунок 6. Модифицированный ответ NTS-сервера (из-за воздействия netsed) в Wireshark
Рисунок 6. Модифицированный ответ NTS-сервера (из-за воздействия netsed) в Wireshark

А вот результат запроса к оракулу этого времени не вернул:

Рисунок 7. Результат запроса к функции GetTimeNts в условиях работы netsed
Рисунок 7. Результат запроса к функции GetTimeNts в условиях работы netsed

Обратившись к логам контейнера, видно и причину:

Рисунок 8. логи контейнера с оракулом в условиях работы netsed
Рисунок 8. логи контейнера с оракулом в условиях работы netsed

Теперь воспользуемся mitmproxy. Сервер NTS работает на порту 4460/TCP. Поэтому правило iptables:

iptables -t nat -A PREROUTING -p tcp -s 172.18.0.0/24 --dport 4460 -m tcp -d 104.131.155.175 -j REDIRECT --to 8080

В консоли mitmproxy видим ошибку (рисунок 9).

Рисунок 9. логи консоли mitmproxy
Рисунок 9. логи консоли mitmproxy

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

Рисунок 10. логи контейнера с оракулом в условиях работы mitmproxy
Рисунок 10. логи контейнера с оракулом в условиях работы mitmproxy

Особенности при взаимодействии с NTS-серверами

Т.к. в процессе соединения с NTS-сервером происходит TLS-соединение, то для его успешного установления у клиента должно быть относительно верное локальное время. Иначе возникнет ошибка (см. Рисунок 11). Например, для сервера ntp1.glypnod.com на момент публикации этой статьи срок действия сертификата с 22.05.2024 по 20.08.2024 . Т.е. у клиента должно быть локальное время в этом диапазоне.

Рисунок 11. локальное время старше времени валидности сертификата для ntp1.glypnod.com
Рисунок 11. локальное время старше времени валидности сертификата для ntp1.glypnod.com

Если невозможно гарантированно добиться относительно верного времени на клиенте, стоит рассмотреть использование NTP внутри криптографически защищённого канала, не требовательного к локальному времени клиента.

Заключение

В результате эксперимента установили, что трафик NTP-сервера подвержен атаке "человек посередине". А используемая в оракуле реализация библиотеки NTS не подвержена такому типу атак. Кроме того, логирование в оракуле позволяет обнаружить попытку атаки. Что, может быть, полезно для своевременного обнаружения инцидента. Оракул времени подходит для использования другими смарт-контрактами блокчейна Hyperledger Fabric и избавляет разработчиков от необходимости самим программировать логику взаимодействия с NTP/NTS серверами.

Комментарии (2)


  1. datacompboy
    29.07.2024 08:22

    Окей. А что на тему подмены уровня "+5 минут" ? TLS не будет затронут.


    1. shanker Автор
      29.07.2024 08:22

      В RFC8915 указано, что подобная атака не слишком затянет время. Кроме того, в коде таймаут соединения с сервером по-умолчанию 5 секунд. По этой причине держать соединение минуты не получится. Далее переход к следующему серверу. Так что минутным задержкам тут неоткуда взяться.

      Ну, и в тестах (например, тут) можно убедиться, что таймаут в 5 секунд отрабатывает корректно.