В рамках данной статьи будут рассмотрены несколько реальных уязвимостей в EOS blockchain (одном из конкурентов Etherum) и то, как они были встроены в конкурс New-Generation Secure Slot Machine на ZeroNights 2018. Если вам интересно познакомиться с тем, как обстоят дела с безопасностью в этой сети blockchain, то welcome под кат.
Вступление
Все началось с того, что недавно, во время аудита смарт-контрактов Etherium на безопасность, один наш знакомый скидывает нам статейку про уязвимости в смарт-контрактах в сети EOS. Нас это сильно заинтересовало, и мы решили разобраться в уязвимостях более детально.
Всё это в итоге и привело к созданию конкурса на ZeroNights 2018 под названием «Однорукий бандит» с уязвимостями в смарт-контракте.
Начнем непосредственно с рассмотрения сети блокчейн EOS, как с ним работать, и как у него все устроено внутри. Статей, описывающих технологию, в Интернете много, поэтому, скорее всего, всех технических деталей не будет, но общий смысл мы постараемся передать так, чтобы и обычный пользователь смог получить элементарное представление о механизмах работы blockchain EOS.
Описание технологии EOS
EOS.io – блокчейн нового поколения от компании Block.one, основанный на концепции PoS (Proof of stake).
Из описания самих создателей сети: «EOS — это бесплатное программное обеспечение сети блокчейн с открытым исходным кодом, который предоставляет разработчикам и предпринимателям платформу для создания, развертывания и запуска высокопроизводительных децентрализованных приложений (DAPP).»
Если в двух словах попытаться объяснить концепцию, то её хорошо отражает выдержка из статьи на Википедии:
Идея proof-of-stake (PoS) заключается в решении проблемы proof-of-work (PoW), связанной с большими тратами электроэнергии. Вместо вычислительных мощностей участников, имеет значение количество криптовалюты, находящейся у них на счету. Так, вместо использования большого количества электроэнергии для решения задачи PoW, у участника PoS ограничен процент возможных проверок транзакций. Ограничение соответствует количеству криптовалюты, находящейся на счету у участника
Сеть совсем новая, и первый запуск главной сети (mainnet) состоялся 10 июня 2018 года. Основной крипто-валютой является EOS, а главный портал для разработчиков developers.eos.io
Блокчейн EOS.io поддерживает приложения, созданные пользователями с использованием кода WebAssembly (WASM – нового веб-стандарта с широкой поддержкой крупных компаний, таких как Google, Microsoft, Apple и других.
На данный момент наиболее свежим инструментарием для создания приложений, которые компилируют код в WASM, является clang / llvm с их компилятором C / C ++.
Для лучшей совместимости разработчики рекомендуют использовать EOSIO CDT (Contract Development Toolkit) – набор утилит от самих разработчиков для удобной и корректной работы над созданием смарт контрактов.
Предыдущий компилятор eosiocpp уже deprecated и не поддерживается, поэтому всем рекомендуется переходить на новый (на момент написания статьи) EOSIO CDT 1.5.
В отличие от эфира
Эфир в своей концепции использует PoW (Proof of Work), что требует дорогостоящих вычислений и награду получает тот, кто первый решил математическую задачу. То есть те, кто решал параллельно, но не успел решить, попусту затратили электроэнергию. В этой ситуации майнеры воюют между собой за более совершенные технологии и оборудование. Чтобы быстрее генерировать блоки и, тем самым, зарабатывать.
В отличие от Эфира в сети ЕОС по концепции PoS создателя нового блока выберет система, и определяется это по количеству личного состояния – доли от общего количества криптовалюты. Таким образом, у кого больше состояние, у того больше шансы быть выбранным системой. Но в отличие от PoW (эфира) вознаграждение за генерацию нового блока отсутствует в принципе, и доход майнеров составляют исключительно комиссии с транзакций.
Вывод? Криптовалюты на базе PoW могут быть в 1000 раз более энергоэфективными.
Разворачиваем среду разработки
Так, с теорией вроде покончили, переходим к практике. А на практике всё выглядит гораздо интереснее. С документацией на момент, когда пытались разобраться летом и начать что-то делать для ZeroNights 2018, было всё совсем плохо, а основной портал для разработки глючил, был наполовину пуст и иногда даже не работал.
Тестовые сети еще толком не были запущены, поэтому пришлось разворачивать свою ноду. Кстати, в отличие от мнения в интернете, завести ее оказалось не так сложно. Пользуясь официальной документацией, мы запустили ее из докера developers.eos.io/eosio-nodeos/docs/docker-quickstart
Расскажем о основных утилитах, программах для работы с блокчейном EOS, с которыми пришлось иметь дело в момент работы над конкурсом:
- Nodeos – собственно, служба самой ноды EOSIO; можно конфигурировать и настраивать различные плагины, например CORS, history и другие.
- Cleos – консольная утилита для работы с нодой, вызовов методов контрактов и взаимодействия с кошельком, ключами, доступами. Самый частый инструмент при работе с EOS.
- Keosd – консольный кошелек, или точнее тоже служба кошелька, хранилище приватных ключей.
- Eosio.cdt – Contract Development Toolkit, так называемый набор утилит разработчика для отладки и компиляции контрактов, генерации ABI-файлов и не только.
- Eos.js – Библиотека Javascript API для удобной работы с нодой и контрактами через веб, встраивается на сайт.
- Scatter – десктоп кошелек для надежного хранения ваших ключей аккаунтов. Существует веб-библиотека scatter.js, которая взаимодействует с десктопным кошельком Scatter по веб-сокетам и тем самым помогает работе с DAPP приложениями в браузере.
Уфф!.. Да, программок много, разобраться в них тоже не совсем легко. Описание всего этого заслуживает отдельного поста и выходит за рамки данной статьи. Но давайте представим, что мы установили ноду на свой сервер и даже научились при помощи cleos вызывать методы контракта, если бы он у нас был.
Да, самое главное. Надо бы нам набросать сам смарт-контракт. Писать мы его будем на C++ и, чтобы сделать хоть что-то толковое, пришлось прочитать немало документации.
Для понимания контрактов везде приводят пример контракта Hello. Основным файлом является hello.cpp и весь контракт описан в нем
#include <eosiolib/eosio.hpp>
using namespace eosio;
class hello : public eosio::contract {
public:
using contract::contract;
/// @abi action
void hi( account_name user ) {
print( "Hello, ", name{user} );
}
};
EOSIO_ABI( hello, (hi) )
Если в двух словах постараться объяснить, то тут – всё просто. Подгружаем библиотеку eosio.hpp, затем создаем класс (он же контракт) hello и унаследуем класс contract. Создаем void метод hi и в параметры заносим переменную user c типом account_name, он же uint64_t. В методе выводим “Hello, ” и имя, которое мы укажем при вызове метода. Последняя строчка, где находится EOSIO_ABI –это вспомогательный макрос, который принимает наш класс и общедоступные методы из этого класса, а также участвует в формировании файла .abi, где указываются все общедоступные методы контракта.
Изучаем уязвимости
Итак, в рамках той статьи, описывалось несколько уязвимостей – давайте их сейчас рассмотрим подробнее.
Numerical Overflow – численное переполнение
При вызове контракта нода проверяет тип параметра, и если данные, которые мы пытаемся ей скормить, не подходят, то нода начнет ругаться и такое бесчинство не пропустит. НО! Если внутри контракта есть какой-нибудь алгоритм изменения числа, сумма чисел или, допустим, умножение, то число может измениться уже внутри контракта. А это значит, что можно указать такое число, которое нода пропустит, а вот контракт умножит, и число выйдет за рамки допустимого типа данных, что и приведет к переполнению.
Что это может дать? К примеру, есть проверка на какой-то числовой параметр, допустим, int Number < 0, и известно, что int у – знаковое число, и если произойдет переполнение числа, то знак числа при больших значениях изменится на отрицательный. Тем самым, проверка будет пройдена переполнением. И тут, конечно, всё зависит от критичности данной проверки.
К примеру, в той же статье про уязвимости есть реальный кейс, где злоумышленники смогли повлиять на параметр balance, тем самым обманув систему. В комментариях к коду более подробно описан механизм взаимодействия с контрактом:
// Структура аккаунтов для вывода баланса
typedef struct acnts {
account_name name0;
account_name name1;
account_name name2;
account_name name3;
} account_names;
// Структура пакетной отправки денег (каждому отправить по 1 EOS)
// Скорее всего, этот метод могли вызвать публично,
// тем самым повлияв на параметр «баланс»
void batchtransfer(
symbol_name symbol,
account_name from,
account_names to,
uint64_t balance
){
// Проверка права исполнителя
require_auth(from);
// Инициализация переменной аккаунта
account fromaccount;
// Проверка существования отправителя и получателей
require_recipient(from);
require_recipient(to.name0);
require_recipient(to.name1);
require_recipient(to.name2);
require_recipient(to.name3);
// Проверка, лежит ли баланс в пределах допустимого диапазона.
// Код функции is_balance_within_range не виден (
eosio_assert(is_balance_within_range(balance), "invalid balance");
// Проверка, больше ли нуля значение переменной «баланс»
// К примеру, «баланс» 1111111111111111 больше 0, и проверка будет пройдена
eosio_assert(balance > 0, "must transfer positive balance");
// Инициализация переменной amount и умножение на 4
// Вот тут и происходит переполнение, и amount становится отрицательным
int64_t amount = balance * 4;
// Поиск в таблице аккаунта from, откуда следует вычитать
int itr = db_find_i64(_self, symbol, N(table), from);
// Проверка, найден ли аккаунт
eosio_assert(itr >= 0, "wrong name");
// Добавляение в переменную fromaccount найденного аккаунта
db_get_i64(itr, &fromaccount, (account));
// Проверка, больше ли отправленного баланс из таблицы
// Например, в игре баланс 0.1 EOS
// и он будет больше, чем отрицательное значение amount
eosio_assert(fromaccount.balance >= amount, "overdrawn balance");
// Функция вычитает
sub_balance(symbol, from, amount);
// Функция отправляет деньги 4 аккаунтам
add_balance(symbol, to.name0, balance);
add_balance(symbol, to.name1, balance);
add_balance(symbol, to.name2, balance);
add_balance(symbol, to.name3, balance);
}
Взлом скорее всего был осуществлен следующим образом. Злоумышленник предварительно создал 4 аккаунта и вызвал метод batchtransfer напрямую, приблизительно так:
cleos push action contractname batchtransfer '{"symbol ":"EOS", "from":”attacker”, "to":{ “name0”:”acc0”, “name1”:”acc1”, “name2”:”acc2”, “name3”:”acc3”}, "balance":"111111111111111111 EOS"}' -p attacker@active
Оговорюсь сразу, это лишь предположение; как точно произвели взлом – мы не знаем, и если будут другие мысли по этому поводу или более точная информация, то пишите в комментариях.
Autherization check, проверка на авторизацию
Отсутствие проверки метода контракта require_auth() на авторизации пользователя приведет к тому, что любой человек, не обладающий нужными правами, сможет воспользоваться привилегированными методами контракта, например, вывод денег с контракта.
Отсутствие проверки прав вызова метода
При отправке на контракт денег (EOS) можно указать в специальном макросе, что будет происходить дальше и что делать. Скажем, при получении денег будет вызываться некий алгоритм, например, запускаться рулетка или еще что-нибудь, а также проверка:
if( code == self || code == N(eosio.token) || action == N(onerror) ) {
TYPE thiscontract( self );
switch( action ) {
EOSIO_API( TYPE, MEMBERS )
}
}
// Отсутствует проверка на action == N(transfer)
В этой проверке нет ограничения вызова метода transfer, из-за чего можно метод трансфер вызвать напрямую, без пересылки денег на контракт. А это означает запуск механизма с дальнейшим выигрышем, не тратя ни копейки.
Конкурс на ZeroNights 2018
Идея конкурса родилась сама по себе: раз всё связано с играми и тремя уязвимостями, следовательно, будем делать игру на механизме смарт-контракта в блокчейне EOS.io. Игра должна быть максимально простой, но интересной.
Игровой автомат «Однорукий бандит»! Всегда удивляли люди, жаждущие легкой наживы — помните, халявы в мире не бывает, или почти не бывает. Тут, кстати, она вполне есть, вернее, появится, когда в ход пойдут уязвимости.
Фронтенд
Фронтенд игры решили сделать модным, красивым и трехмерным. Спасибо vtornik23, за то, что не отказался поучаствовать и помог нам сделать полностью фронтенд на Unity3d движке.
Трехмерный игровой аппарат «однорукий бандит»; отправив на него 1 ЕОС и дернув за шикарный рычаг, игрок получает возможность запустить колесо фортуны и сорвать куш!
Уязвимости контракта
По задумке игры, выигрышем считалось выпадение трёх матрёшек ZeroNights, что в числовом коэффициенте будет либо 777, либо 0.Шансы на выигрыш приравнивались к 0.02%, и некий невнимательный программист попытался усложнить алгоритм рандома, добавив в него всего лишь умножение (multiplication overflow) на количество присланных денег, и поленился обдумывать условия детальнее, поэтому просто написал if (result == 777 || result < 1 ), что дает возможность подсунуть отрицательное значение.
int rnd = random(999);
int result = rnd * price.amount;
uint64_t prize = 0;
print("Result:", result);
// BINGO 777 or 000 !!! ~ 0.02%
if(result == 777 || result < 1 ) {
prize = 100;
sendtokens(from);
}
Сам смарт-контракт выложен на гитхаб, так что все желающие могут его повнимательнее рассмотреть со всех сторон и определить остальные уязвимости. О них уже написано чуть выше, так что сложностей в их поиске быть не должно.
Правила участия
Правила участия очень просты: необходимо было попытаться выиграть или взломать механизмы системы. При выпадении 3 матрешек – Джек-пот!!! Система начисляет 100 единиц крипто-валюты. Если участник получает джек-пот 3 раза подряд, он становится победителем и получает призы от организаторов — фирменные худи, значки, разнообразный мерч.
Конечно, можно было выиграть, долго дергая рычаг и надеясь на удачу, но фортуна — штука непредсказуемая, да и процент выигрыша очень мал, так что проще было взломать.
Результаты конкурса
В итоге конкурс, на наш взгляд, прошел идеально. Были запланированы награды для 3 человек, и как раз троим удалось справиться с конкурсом до назначенной даты окончания. Конкурс проводился 2 дня, в течение которых участники должны были решить таск. Официальное награждение и вручение подарков было на закрытии конференции на главной сцене ZeroNights 2018.
Основной упор делался на познавание технологии блокчейн ЕОС, и нами была оставлена пара подсказок, одну из которых так никому не удалось найти. Эту загадку мы оставим на потом…
Отзывы участников
Алексей (1 место)
ZeroNights одна из моих любимых конференций, начиная с самой первой, в Петербурге я не пропустил ни одну. Всегда дает заряд энтузиазма на полгода точно, а там весной PHDays :). Последние 3 года я занимаюсь блокчейн разработкой. В этом году блокчейн добрался и до ZeroNights (в прошлом правда вроде тоже, было на хаквесте, но я его пропустил). Первым делом, после регистрации на конференции я пошел посмотреть, что и как там с блокчейном. Думал будет, что-то на подобие как на PHDays, какой-нибудь кривой рандом или race condition на эфире. Но тут оказался EOS, с которым у меня было небольшое знакомство на первом хакатоне EOS, но оно было не продолжительным, и к тому же все настройки для разработки были утеряны. Боевой настрой упал, и я пошел ждать начала конференции. Но любопытство взяло верх, все-таки что же там с EOS-ом не так!
Stanislav Povolotsky (2 место)
Для меня это был долгий, но интересный конкурс. И он стал замечательной возможностью познакомиться поближе с архитектурой блокчейна EOS. Конкурс начался с удивления, что в сеть EOS (mainnet) просто так не попасть — только за $$$. После подсказки, что контракт развёрнут в тестовой сети, регистрации в этой сети, настройки scatter и просмотра истории транзакций для игрового контракта — стало сразу понятно, как нужно обманывать слот-машину (автор контракта при тестировании делал это несколько раз). Но уверенность в том, что так быстро и просто удастся справиться с конкурсом быстро улетучилась, как только сеть не одобрила все мои транзакции с параметрами, идентичными выигрышной транзакции.
Ирина (3 место)
До участия в конкурсе представляла работу смарт-контрактов только в теории, поэтому было очень интересно «встретиться с ними вживую», увидеть исходный код, опробовать инструменты (и в очередной раз убедиться, что python лучше всего)). Задание получилось действительно очень захватывающим. Спасибо!
И в заключение
Не скажем, что все справились легко. Для кого-то это были сложные 2 дня, и только под конец счастливчикам удалось победить, используя недостатки любого блокчейна — если информация попала в блокчейн, то доступна каждому, и если кто-то уже что-то взломал, то и другой может посмотреть его путь.
Благодарим всех участников и тех, кто помогал в организации конкурса.
До встречи на ZeroNights 2019, вас будут ждать новые приключения!
Автор: 5aava