- PVSM.RU - https://www.pvsm.ru -

В рамках данной статьи будут рассмотрены несколько реальных уязвимостей в EOS blockchain (одном из конкурентов Etherum) и то, как они были встроены в конкурс New-Generation Secure Slot Machine [1] на ZeroNights 2018. Если вам интересно познакомиться с тем, как обстоят дела с безопасностью в этой сети blockchain, то welcome под кат.
Все началось с того, что недавно, во время аудита смарт-контрактов Etherium на безопасность, один наш знакомый скидывает нам статейку [2] про уязвимости в смарт-контрактах в сети EOS. Нас это сильно заинтересовало, и мы решили разобраться в уязвимостях более детально.
Всё это в итоге и привело к созданию конкурса на ZeroNights 2018 под названием «Однорукий бандит» с уязвимостями в смарт-контракте.
Начнем непосредственно с рассмотрения сети блокчейн EOS, как с ним работать, и как у него все устроено внутри. Статей, описывающих технологию, в Интернете много, поэтому, скорее всего, всех технических деталей не будет, но общий смысл мы постараемся передать так, чтобы и обычный пользователь смог получить элементарное представление о механизмах работы blockchain EOS.
EOS.io – блокчейн нового поколения от компании Block.one [3], основанный на концепции PoS (Proof of stake [4]).
Из описания самих создателей сети: «EOS — это бесплатное программное обеспечение сети блокчейн с открытым исходным кодом, который предоставляет разработчикам и предпринимателям платформу для создания, развертывания и запуска высокопроизводительных децентрализованных приложений (DAPP).»
Если в двух словах попытаться объяснить концепцию, то её хорошо отражает выдержка из статьи [4] на Википедии:
Идея proof-of-stake (PoS) заключается в решении проблемы proof-of-work (PoW), связанной с большими тратами электроэнергии. Вместо вычислительных мощностей участников, имеет значение количество криптовалюты, находящейся у них на счету. Так, вместо использования большого количества электроэнергии для решения задачи PoW, у участника PoS ограничен процент возможных проверок транзакций. Ограничение соответствует количеству криптовалюты, находящейся на счету у участника
Сеть совсем новая, и первый запуск главной сети (mainnet) состоялся 10 июня 2018 года. Основной крипто-валютой является EOS, а главный портал для разработчиков developers.eos.io [5]
Блокчейн 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 [6]
Расскажем о основных утилитах, программах для работы с блокчейном EOS, с которыми пришлось иметь дело в момент работы над конкурсом:
Уфф!.. Да, программок много, разобраться в них тоже не совсем легко. Описание всего этого заслуживает отдельного поста и выходит за рамки данной статьи. Но давайте представим, что мы установили ноду на свой сервер и даже научились при помощи cleos вызывать методы контракта, если бы он у нас был.
Да, самое главное. Надо бы нам набросать сам смарт-контракт. Писать мы его будем на C++ и, чтобы сделать хоть что-то толковое, пришлось прочитать немало документации.
Для понимания контрактов везде приводят пример контракта Hello [13]. Основным файлом является 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, где указываются все общедоступные методы контракта.
Итак, в рамках той статьи [2], описывалось несколько уязвимостей – давайте их сейчас рассмотрим подробнее.
При вызове контракта нода проверяет тип параметра, и если данные, которые мы пытаемся ей скормить, не подходят, то нода начнет ругаться и такое бесчинство не пропустит. НО! Если внутри контракта есть какой-нибудь алгоритм изменения числа, сумма чисел или, допустим, умножение, то число может измениться уже внутри контракта. А это значит, что можно указать такое число, которое нода пропустит, а вот контракт умножит, и число выйдет за рамки допустимого типа данных, что и приведет к переполнению.
Что это может дать? К примеру, есть проверка на какой-то числовой параметр, допустим, int Number < 0, и известно, что int у – знаковое число, и если произойдет переполнение числа, то знак числа при больших значениях изменится на отрицательный. Тем самым, проверка будет пройдена переполнением. И тут, конечно, всё зависит от критичности данной проверки.
К примеру, в той же статье про уязвимости есть реальный кейс [15], где злоумышленники смогли повлиять на параметр 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

Оговорюсь сразу, это лишь предположение; как точно произвели взлом – мы не знаем, и если будут другие мысли по этому поводу или более точная информация, то пишите в комментариях.
Отсутствие проверки метода контракта 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, из-за чего можно метод трансфер вызвать напрямую, без пересылки денег на контракт. А это означает запуск механизма с дальнейшим выигрышем, не тратя ни копейки.
Идея конкурса родилась сама по себе: раз всё связано с играми и тремя уязвимостями, следовательно, будем делать игру на механизме смарт-контракта в блокчейне EOS.io. Игра должна быть максимально простой, но интересной.
Игровой автомат «Однорукий бандит»! Всегда удивляли люди, жаждущие легкой наживы — помните, халявы в мире не бывает, или почти не бывает. Тут, кстати, она вполне есть, вернее, появится, когда в ход пойдут уязвимости.
Фронтенд игры решили сделать модным, красивым и трехмерным. Спасибо vtornik23 [16], за то, что не отказался поучаствовать и помог нам сделать полностью фронтенд на 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);
}
Сам смарт-контракт выложен на гитхаб [17], так что все желающие могут его повнимательнее рассмотреть со всех сторон и определить остальные уязвимости. О них уже написано чуть выше, так что сложностей в их поиске быть не должно.
Правила участия очень просты: необходимо было попытаться выиграть или взломать механизмы системы. При выпадении 3 матрешек – Джек-пот!!! Система начисляет 100 единиц крипто-валюты. Если участник получает джек-пот 3 раза подряд, он становится победителем и получает призы от организаторов — фирменные худи, значки, разнообразный мерч.
Конечно, можно было выиграть, долго дергая рычаг и надеясь на удачу, но фортуна — штука непредсказуемая, да и процент выигрыша очень мал, так что проще было взломать.
В итоге конкурс, на наш взгляд, прошел идеально. Были запланированы награды для 3 человек, и как раз троим удалось справиться с конкурсом до назначенной даты окончания. Конкурс проводился 2 дня, в течение которых участники должны были решить таск. Официальное награждение и вручение подарков было на закрытии конференции на главной сцене ZeroNights 2018.
Основной упор делался на познавание технологии блокчейн ЕОС, и нами была оставлена пара подсказок, одну из которых так никому не удалось найти. Эту загадку мы оставим на потом…
Алексей [18] (1 место)
ZeroNights одна из моих любимых конференций, начиная с самой первой, в Петербурге я не пропустил ни одну. Всегда дает заряд энтузиазма на полгода точно, а там весной PHDays :). Последние 3 года я занимаюсь блокчейн разработкой. В этом году блокчейн добрался и до ZeroNights (в прошлом правда вроде тоже, было на хаквесте, но я его пропустил). Первым делом, после регистрации на конференции я пошел посмотреть, что и как там с блокчейном. Думал будет, что-то на подобие как на PHDays, какой-нибудь кривой рандом или race condition на эфире. Но тут оказался EOS, с которым у меня было небольшое знакомство на первом хакатоне EOS, но оно было не продолжительным, и к тому же все настройки для разработки были утеряны. Боевой настрой упал, и я пошел ждать начала конференции. Но любопытство взяло верх, все-таки что же там с EOS-ом не так!
Stanislav Povolotsky [19] (2 место)
Для меня это был долгий, но интересный конкурс. И он стал замечательной возможностью познакомиться поближе с архитектурой блокчейна EOS. Конкурс начался с удивления, что в сеть EOS (mainnet) просто так не попасть — только за $$$. После подсказки, что контракт развёрнут в тестовой сети, регистрации в этой сети, настройки scatter и просмотра истории транзакций для игрового контракта — стало сразу понятно, как нужно обманывать слот-машину (автор контракта при тестировании делал это несколько раз). Но уверенность в том, что так быстро и просто удастся справиться с конкурсом быстро улетучилась, как только сеть не одобрила все мои транзакции с параметрами, идентичными выигрышной транзакции.
Ирина [20] (3 место)
До участия в конкурсе представляла работу смарт-контрактов только в теории, поэтому было очень интересно «встретиться с ними вживую», увидеть исходный код, опробовать инструменты (и в очередной раз убедиться, что python лучше всего)). Задание получилось действительно очень захватывающим. Спасибо!
Не скажем, что все справились легко. Для кого-то это были сложные 2 дня, и только под конец счастливчикам удалось победить, используя недостатки любого блокчейна — если информация попала в блокчейн, то доступна каждому, и если кто-то уже что-то взломал, то и другой может посмотреть его путь.
Благодарим всех участников и тех, кто помогал в организации конкурса.
До встречи на ZeroNights 2019, вас будут ждать новые приключения!
Автор: 5aava
Источник [21]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/302707
Ссылки в тексте:
[1] конкурс New-Generation Secure Slot Machine: https://2018.zeronights.ru/activities/%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D1%8B%D0%B9-%D0%B8%D0%B3%D1%80%D0%BE%D0%B2%D0%BE%D0%B9-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82-%D0%BD%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE/
[2] статейку: https://github.com/slowmist/eos-smart-contract-security-best-practices/blob/master/README_EN.md
[3] Block.one: https://block.one/
[4] Proof of stake: https://ru.wikipedia.org/wiki/%D0%94%D0%BE%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D1%82%D0%B2%D0%BE_%D0%B4%D0%BE%D0%BB%D0%B8_%D0%B2%D0%BB%D0%B0%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F
[5] developers.eos.io: https://developers.eos.io
[6] developers.eos.io/eosio-nodeos/docs/docker-quickstart: https://developers.eos.io/eosio-nodeos/docs/docker-quickstart
[7] Nodeos: https://developers.eos.io/eosio-nodeos/docs/
[8] Cleos: https://developers.eos.io/eosio-nodeos/docs/cleos-overview
[9] Keosd: https://developers.eos.io/keosd/docs/
[10] Eosio.cdt: https://github.com/EOSIO/eosio.cdt
[11] Eos.js: https://github.com/EOSIO/eosjs
[12] Scatter: https://get-scatter.com/
[13] Hello: https://github.com/EOSIO/eos/tree/master/contracts/hello
[14] численное переполнение: https://ru.wikipedia.org/wiki/%D0%A6%D0%B5%D0%BB%D0%BE%D1%87%D0%B8%D1%81%D0%BB%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D0%B5%D1%80%D0%B5%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5
[15] реальный кейс: https://bihu.com/article/995093
[16] vtornik23: https://habr.com/users/vtornik23/
[17] гитхаб: https://github.com/5aava/SecureSlot
[18] Алексей: https://twitter.com/bitaps_com
[19] Stanislav Povolotsky: https://twitter.com/StanPov
[20] Ирина: https://vk.com/irina4711
[21] Источник: https://habr.com/post/433552/?utm_source=habrahabr&utm_medium=rss&utm_campaign=433552
Нажмите здесь для печати.