Лучшие практики по безопасной разработке смарт-контрактов EOS
Оригинал: https://github.com/slowmist/eos-smart-contract-security-best-practices/blob/master/README_EN.md
На английский перевел Kai Jing (с EOS42)
Цель этого документа - представить некоторые рекомендации по безопасности разработчиков для смарт-контрактов EOS и проанализировать некоторые известные уязвимости контракта. Мы будем благодарны сообществу за любые предложения по внесению изменений или усовершенствованию этого документа, а также приветствуем различные виды пулл реквестов. Вы также можете добавлять любые релевантные, уже ранее опубликованные статьи или блоги, просто добавьте их в перечень ресурсов.
Директории
Рекомендации по безопасности
EOS все еще находится на ранних стадиях развития и имеет некоторые экспериментальные характеристики. В результате, вместе с обнаружением новых ошибок и уязвимостей безопасности, а также разработкой новых функций, угрозы безопасности, с которыми мы сталкиваемся, постоянно меняются. Эта статья - это всего лишь начальная точка, которая должна помочь разработчикам создавать безопасные смарт-контракты.
Разработка смарт-контрактов требует нового типа инженерного мышления, что отличается от разработки наших предыдущих проектов. Поскольку цена ошибки может быть слишком высока, очень сложно компенсировать это через правки, так как это делается через централизованное программное обеспечение. Как и в случае hardware-программирования или разработки программного обеспечения для финансовых сервисов, у вас есть более сложные задачи, чем веб разработка или мобильная разработка. Поэтому, недостаточно защищаться только от явных уязвимостей, нужно также изучать новые концепции разработки:
- Будьте готовы к тому, что случаются ошибки. Любой значимый смарт-контракт – более или менее ошибочен, поэтому ваш код должен уметь правильно реагировать на возникающие баги и уязвимости. Всегда следуйте этим правилам:
- останавливайте смарт-контракт при возникновении ошибки.
- учитывайте риски, связанные с аккаунтом, например, устанавливайте лимит и максимальный лимит на передачу.
- найдите эффективные способы исправления багов и улучшения функциональности.
- Будьте осторожны с релизом смарт-контрактов. Постарайтесь сделать все возможное, чтобы находить и исправлять возможные баги до официального релиза смарт-контракта.
- Тщательно протестируйте смарт-контракты и тестируйте их повторно, каждый раз когда обнаружена новая атака (также тестируя те контракты, которые были выпущены).
- Привлеките профессиональный сервис по аудиту безопасности и предоставляйте программу Bug Bounty с самого начала релиза альфа-версии на cryptokylin-testnet, Jungle-testnet или других публичных тестовых сетях.
- Релизьте в несколько этапов, на каждом их которых должно быть обеспечено адекватное тестирование.
- Делайте смарт-контракты простыми. Повышенная сложность повысит риск возникновения ошибки.
- Сделайте так, чтобы логика смарт-контрактов была простой.
- Убедитесь, что контракты и функции являются модульными.
- Используйте контракты или инструменты, которые уже широко используются (например, не пишите рандомайзер самостоятельно).
- Когда это допускается, ясность должна быть важнее эффективности.
- Используйте блокчейн технологию только для децентрализованной части вашей системы.
- Постоянно обновляйте информацию. Обеспечьте доступ к самым последним обновлениям по безопасности путем раскрытия ресурсов.
- Проверяйте свой смарт-контракт при обнаружении любых новых уязвимостей.
- Обновляйте библиотеку или инструмент настолько быстро, насколько это возможно.
- Используйте новейшие технологии обеспечения безопасности.
- Изучите все функции блокчейна. Несмотря на то, что ваш предыдущий опыт программирования также применим к разработке смарт-контрактов, там все еще есть подводный камни, за которыми нужно следить:
require\_recipient (account\_name name)
выдает уведомление и активирует функцию с тем же именем вname
контракте (если имя аккаунта уже развернуло контракт), вы можете найти официальный документ здесь.
Известные уязвимости
Численное переполнение
Если не проверить границы при выполнении арифметических операций, это может привести к переполнению значений, что вызывает потерю активов пользователей.
Пример уязвимости
коды с уязвимостью: batchtransfer
пакетная передача
account_name name0;
account_name name1;
account_name name2;
account_name name3;
} account_names;
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);
eosio_assert(is_balance_within_range(balance), "invalid balance");
eosio_assert(balance > 0, "must transfer positive balance");
uint64_t amount = balance * 4; //Multiplication overflow
int itr = db_find_i64(_self, symbol, N(table), from);
eosio_assert(itr >= 0, "Sub-- wrong name");
db_get_i64(itr, &fromaccount, (account));
eosio_assert(fromaccount.balance >= amount, "overdrawn balance");
sub_balance(symbol, from, amount);
add_balance(symbol, to.name0, balance);
add_balance(symbol, to.name1, balance);
add_balance(symbol, to.name2, balance);
add_balance(symbol, to.name3, balance);
}
Метод защиты
По возможности, используйте скорее asset
структуру для операций, а не balance
для операций.
Реальный случай
Проверка авторизации
При выполнении соответствующих операций, с точностью определяйте, соответствуют ли параметры, переданные в функцию, фактическому вызову, используйте require_auth
для проверки авторизации.
Пример уязвимости
коды с уязвимостью: transfer
account_name to,
asset quantity,
string memo )
{
eosio_assert( from != to, "cannot transfer to self" );
eosio\_assert( is\_account( to ), "to account does not exist");
auto sym = quantity.symbol.name();
stats statstable( _self, sym );
const auto& st = statstable.get( sym );
require_recipient( from );
require_recipient( to );
eosio_assert( quantity.is_valid(), "invalid quantity" );
eosio_assert( quantity.amount > 0, "must transfer positive quantity" );
eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" );
auto payer = has_auth( to ) ? to : from;
sub_balance( from, quantity );
add_balance( to, quantity, payer );
}
Метод защиты
Используйте метод require_auth (from)
для проверки совместимости аккаунта переноса активов с вызывающим аккаунтом.
Реальный случай
Нет
Применение проверки
При обработке контрактных вызовов, убедитесь, что каждое действие и коды соответствуют требованиям.
Пример уязвимости
коды с уязвимостью:
[#define](/trending/define) EOSIO\_ABI\_EX( TYPE, MEMBERS )
extern "C" {
void apply( uint64\_t receiver, uint64\_t code, uint64_t action ) {
auto self = receiver;
if( action == N(onerror)) {
/\* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission _/
eosio_assert(code == N(eosio), "onerror action's are only valid from the "eosio" system account");
}
if( code == self || code == N(eosio.token) || action == N(onerror) ) {
TYPE thiscontract( self );
switch( action ) {
EOSIO_API( TYPE, MEMBERS )
}
/_ does not allow destructor of thiscontract to run: eosio_exit(0); */
}
}
}
EOSIO\_ABI\_EX(eosio::charity, (hi)(transfer))
Метод защиты
Используйте приведенные ниже коды:
if( ((code == self && action != N(transfer) ) || (code == N(eosio.token) && action == N(transfer)) || action == N(onerror)) ) { }
Привяжите каждое основное действие и код к соответствующим требованиям, чтобы избежать аномальных и незаконных вызовов.
Реальный случай
EOSBet Transfer Hack Statement
Перевод CryptoLions
Website
Telegram
Steemit
Twitter
GitHub
Meetup