Перейти к основному контенту
Change page

Безопасность смарт-контрактов

Смарт-контракты чрезвычайно гибки и способны контролировать большие объемы ценностей и данных, выполняя неизменяемую логику на основе кода, развернутого в блокчейне. Это создало динамичную экосистему не требующих доверия и децентрализованных приложений, которые предоставляют множество преимуществ по сравнению с устаревшими системами. Они также представляют собой возможности для злоумышленников, стремящихся получить прибыль за счет эксплуатации уязвимостей в смарт-контрактах.

Публичные блокчейны, такие как Эфириум, еще больше усложняют проблему обеспечения безопасности смарт-контрактов. Развернутый код контракта обычно не может быть изменен для исправления недостатков безопасности, в то время как активы, украденные из смарт-контрактов, крайне сложно отследить и в большинстве случаев невозможно вернуть из-за неизменяемости.

Хотя цифры разнятся, по оценкам, общая сумма ценностей, украденных или потерянных из-за дефектов безопасности в смарт-контрактах, легко превышает 1 миллиард долларов. Сюда входят такие громкие инциденты, как взлом The DAO (opens in a new tab) (украдено 3,6 млн ETH, что по сегодняшним ценам составляет более 1 млрд долларов), взлом кошелька с мультиподписью Parity (opens in a new tab) (хакеры украли 30 млн долларов) и проблема с замороженным кошельком Parity (opens in a new tab) (более 300 млн долларов в ETH заблокированы навсегда).

Вышеупомяненные проблемы делают крайне важным для разработчиков вкладывать усилия в создание безопасных, надежных и отказоустойчивых смарт-контрактов. Безопасность смарт-контрактов — это серьезное дело, и каждому разработчику будет полезно его изучить. В этом руководстве будут рассмотрены вопросы безопасности для разработчиков Эфириума и изучены ресурсы для повышения безопасности смарт-контрактов.

Предварительные требования

Убедитесь, что вы знакомы с основами разработки смарт-контрактов, прежде чем переходить к вопросам безопасности.

Руководства по созданию безопасных смарт-контрактов Эфириума

1. Разработайте надлежащий контроль доступа

В смарт-контрактах функции, помеченные как public или external, могут быть вызваны любыми внешними аккаунтами (EOA) или аккаунтами контрактов. Указание публичной видимости для функций необходимо, если вы хотите, чтобы другие взаимодействовали с вашим контрактом. Однако функции, помеченные как private, могут быть вызваны только функциями внутри смарт-контракта, а не внешними аккаунтами. Предоставление каждому участнику сети доступа к функциям контракта может вызвать проблемы, особенно если это означает, что любой может выполнять конфиденциальные операции (например, чеканку новых токенов).

Чтобы предотвратить несанкционированное использование функций смарт-контракта, необходимо реализовать безопасный контроль доступа. Механизмы контроля доступа ограничивают возможность использования определенных функций в смарт-контракте одобренными субъектами, такими как аккаунты, ответственные за управление контрактом. Шаблон Ownable и управление на основе ролей — это два шаблона, полезных для реализации контроля доступа в смарт-контрактах:

Шаблон Ownable

В шаблоне Ownable адрес устанавливается в качестве «владельца» контракта в процессе создания контракта. Защищенным функциям назначается модификатор OnlyOwner, который гарантирует, что контракт аутентифицирует личность вызывающего адреса перед выполнением функции. Вызовы защищенных функций с других адресов, кроме владельца контракта, всегда откатываются, предотвращая нежелательный доступ.

Контроль доступа на основе ролей

Регистрация одного адреса в качестве Owner в смарт-контракте создает риск централизации и представляет собой единую точку отказа. Если ключи аккаунта владельца скомпрометированы, злоумышленники могут атаковать принадлежащий ему контракт. Вот почему использование шаблона контроля доступа на основе ролей с несколькими административными аккаунтами может быть лучшим вариантом.

При контроле доступа на основе ролей доступ к конфиденциальным функциям распределяется между набором доверенных участников. Например, один аккаунт может отвечать за чеканку токенов, в то время как другой аккаунт выполняет обновления или приостанавливает контракт. Децентрализация контроля доступа таким образом устраняет единые точки отказа и снижает допущения о доверии для пользователей.

Использование кошельков с мультиподписью

Другой подход к реализации безопасного контроля доступа — использование аккаунта с мультиподписью для управления контрактом. В отличие от обычного EOA, аккаунты с мультиподписью принадлежат нескольким субъектам и требуют подписей от минимального количества аккаунтов — скажем, 3 из 5 — для выполнения транзакций.

Использование мультисига для контроля доступа вводит дополнительный уровень безопасности, поскольку действия с целевым контрактом требуют согласия нескольких сторон. Это особенно полезно, если необходимо использовать шаблон Ownable, так как это усложняет злоумышленнику или недобросовестному инсайдеру манипулирование конфиденциальными функциями контракта в злонамеренных целях.

2. Используйте операторы require(), assert() и revert() для защиты операций контракта

Как уже упоминалось, любой может вызывать публичные функции в вашем смарт-контракте после его развертывания в блокчейне. Поскольку вы не можете заранее знать, как внешние аккаунты будут взаимодействовать с контрактом, в идеале следует реализовать внутренние меры защиты от проблемных операций перед развертыванием. Вы можете обеспечить правильное поведение в смарт-контрактах, используя операторы require(), assert() и revert() для вызова исключений и отката изменений состояния, если выполнение не удовлетворяет определенным требованиям.

require(): require определяются в начале функций и гарантируют выполнение заранее заданных условий перед выполнением вызванной функции. Оператор require может использоваться для проверки пользовательского ввода, проверки переменных состояния или аутентификации личности вызывающего аккаунта перед продолжением выполнения функции.

assert(): assert() используется для обнаружения внутренних ошибок и проверки нарушений «инвариантов» в вашем коде. Инвариант — это логическое утверждение о состоянии контракта, которое должно оставаться истинным для всех выполнений функций. Примером инварианта является максимальное общее предложение или баланс контракта токена. Использование assert() гарантирует, что ваш контракт никогда не достигнет уязвимого состояния, а если это произойдет, все изменения переменных состояния будут отменены.

revert(): revert() может использоваться в операторе if-else, который вызывает исключение, если требуемое условие не выполняется. В приведенном ниже примере контракта используется revert() для защиты выполнения функций:

3. Тестируйте смарт-контракты и проверяйте правильность кода

Неизменяемость кода, работающего в виртуальной машине Эфириума, означает, что смарт-контракты требуют более высокого уровня оценки качества на этапе разработки. Тщательное тестирование вашего контракта и наблюдение за ним на предмет любых неожиданных результатов значительно повысят безопасность и защитят ваших пользователей в долгосрочной перспективе.

Обычный метод заключается в написании небольших модульных тестов с использованием фиктивных данных, которые контракт должен получать от пользователей. Модульное тестирование хорошо подходит для проверки функциональности определенных функций и обеспечения того, чтобы смарт-контракт работал должным образом.

К сожалению, модульное тестирование минимально эффективно для повышения безопасности смарт-контрактов при изолированном использовании. Модульный тест может доказать, что функция выполняется правильно для фиктивных данных, но модульные тесты эффективны лишь настолько, насколько хороши написанные тесты. Это затрудняет обнаружение пропущенных крайних случаев и уязвимостей, которые могут нарушить безопасность вашего смарт-контракта.

Лучший подход — объединить модульное тестирование с тестированием на основе свойств, выполняемым с использованием статического и динамического анализа. Статический анализ опирается на низкоуровневые представления, такие как графы потока управления (opens in a new tab) и абстрактные синтаксические деревья (opens in a new tab), для анализа достижимых состояний программы и путей выполнения. Между тем, методы динамического анализа, такие как фаззинг смарт-контрактов (opens in a new tab), выполняют код контракта со случайными входными значениями для обнаружения операций, нарушающих свойства безопасности.

Формальная верификация — это еще один метод проверки свойств безопасности в смарт-контрактах. В отличие от обычного тестирования, формальная верификация может убедительно доказать отсутствие ошибок в смарт-контракте. Это достигается путем создания формальной спецификации, которая фиксирует желаемые свойства безопасности, и доказательства того, что формальная модель контрактов соответствует этой спецификации.

4. Запросите независимую проверку вашего кода

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

Аудиты

Заказ аудита смарт-контракта — это один из способов проведения независимой проверки кода. Аудиторы играют важную роль в обеспечении безопасности смарт-контрактов и отсутствии в них дефектов качества и ошибок проектирования.

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

Программы bug bounty

Настройка программы bug bounty — это еще один подход к реализации внешних проверок кода. Bug bounty — это финансовое вознаграждение, предоставляемое лицам (обычно «белым» хакерам), которые обнаруживают уязвимости в приложении.

При правильном использовании программы bug bounty дают членам хакерского сообщества стимул проверять ваш код на наличие критических недостатков. Реальным примером является «ошибка бесконечных денег», которая позволила бы злоумышленнику создать неограниченное количество эфира в Optimism (opens in a new tab), протоколе уровня 2 (l2), работающем на Эфириуме. К счастью, «белый» хакер обнаружил недостаток (opens in a new tab) и уведомил команду, получив при этом крупную выплату (opens in a new tab).

Полезная стратегия заключается в том, чтобы установить выплату по программе bug bounty пропорционально сумме поставленных на кон средств. Этот подход, описываемый как «масштабируемое bug bounty (opens in a new tab)», предоставляет финансовые стимулы для ответственного раскрытия уязвимостей вместо их эксплуатации.

5. Следуйте передовым практикам при разработке смарт-контрактов

Наличие аудитов и программ bug bounty не освобождает вас от ответственности за написание высококачественного кода. Хорошая безопасность смарт-контрактов начинается с соблюдения надлежащих процессов проектирования и разработки:

  • Храните весь код в системе контроля версий, такой как git

  • Вносите все изменения в код через pull request (запросы на слияние)

  • Убедитесь, что у pull request есть как минимум один независимый рецензент — если вы работаете над проектом в одиночку, подумайте о том, чтобы найти других разработчиков и обмениваться проверками кода

  • Используйте среду разработки для тестирования, компиляции и развертывания смарт-контрактов

  • Прогоняйте свой код через базовые инструменты анализа кода, такие как Cyfrin Aderyn (opens in a new tab), Mythril и Слизер. В идеале вы должны делать это перед слиянием каждого pull request и сравнивать различия в результатах

  • Убедитесь, что ваш код компилируется без ошибок, а компилятор Solidity не выдает предупреждений

  • Должным образом документируйте свой код (используя NatSpec (opens in a new tab)) и описывайте детали архитектуры контракта понятным языком. Это облегчит другим аудит и проверку вашего кода.

6. Реализуйте надежные планы аварийного восстановления

Разработка безопасного контроля доступа, реализация модификаторов функций и другие предложения могут повысить безопасность смарт-контрактов, но они не могут исключить возможность злонамеренных эксплойтов. Создание безопасных смарт-контрактов требует «подготовки к сбою» и наличия резервного плана для эффективного реагирования на атаки. Надлежащий план аварийного восстановления будет включать некоторые или все из следующих компонентов:

Обновления контрактов

Хотя смарт-контракты Эфириума по умолчанию неизменяемы, можно достичь некоторой степени изменяемости, используя шаблоны обновления. Обновление контрактов необходимо в тех случаях, когда критический недостаток делает ваш старый контракт непригодным для использования, и развертывание новой логики является наиболее целесообразным вариантом.

Механизмы обновления контрактов работают по-разному, но «шаблон прокси» является одним из наиболее популярных подходов к обновлению смарт-контрактов. Шаблоны прокси (opens in a new tab) разделяют состояние и логику приложения между двумя контрактами. Первый контракт (называемый «прокси-контракт») хранит переменные состояния (например, балансы пользователей), в то время как второй контракт (называемый «логический контракт») содержит код для выполнения функций контракта.

Аккаунты взаимодействуют с прокси-контрактом, который отправляет все вызовы функций в логический контракт с использованием низкоуровневого вызова delegatecall() (opens in a new tab). В отличие от обычного вызова сообщения, delegatecall() гарантирует, что код, работающий по адресу логического контракта, выполняется в контексте вызывающего контракта. Это означает, что логический контракт всегда будет писать в хранилище прокси (вместо своего собственного хранилища), а исходные значения msg.sender и msg.value сохраняются.

Делегирование вызовов логическому контракту требует хранения его адреса в хранилище прокси-контракта. Следовательно, обновление логики контракта — это лишь вопрос развертывания другого логического контракта и сохранения нового адреса в прокси-контракте. Поскольку последующие вызовы прокси-контракта автоматически направляются в новый логический контракт, вы «обновите» контракт без фактического изменения кода.

Подробнее об обновлении контрактов.

Аварийные остановки

Как уже упоминалось, всесторонний аудит и тестирование не могут обнаружить все ошибки в смарт-контракте. Если уязвимость появляется в вашем коде после развертывания, исправить ее невозможно, поскольку вы не можете изменить код, работающий по адресу контракта. Кроме того, реализация механизмов обновления (например, шаблонов прокси) может занять время (они часто требуют одобрения от разных сторон), что только дает злоумышленникам больше времени для нанесения большего ущерба.

Радикальный вариант — реализовать функцию «аварийной остановки», которая блокирует вызовы уязвимых функций в контракте. Аварийные остановки обычно включают следующие компоненты:

  1. Глобальная логическая переменная, указывающая, находится ли смарт-контракт в остановленном состоянии или нет. Эта переменная устанавливается в false при настройке контракта, но изменится на true после остановки контракта.

  2. Функции, которые ссылаются на логическую переменную при своем выполнении. Такие функции доступны, когда смарт-контракт не остановлен, и становятся недоступными при срабатывании функции аварийной остановки.

  3. Субъект, имеющий доступ к функции аварийной остановки, которая устанавливает логическую переменную в true. Для предотвращения злонамеренных действий вызовы этой функции могут быть ограничены доверенным адресом (например, владельцем контракта).

Как только контракт активирует аварийную остановку, определенные функции нельзя будет вызвать. Это достигается путем обертывания выбранных функций в модификатор, который ссылается на глобальную переменную. Ниже приведен пример (opens in a new tab), описывающий реализацию этого шаблона в контрактах:

Этот пример показывает основные особенности аварийных остановок:

  • isStopped — это логическая переменная, которая имеет значение false в начале и true, когда контракт переходит в аварийный режим.

  • Модификаторы функций onlyWhenStopped и stoppedInEmergency проверяют переменную isStopped. stoppedInEmergency используется для управления функциями, которые должны быть недоступны, когда контракт уязвим (например, deposit()). Вызовы этих функций будут просто откатываться.

onlyWhenStopped используется для функций, которые должны быть доступны для вызова во время аварийной ситуации (например, emergencyWithdraw()). Такие функции могут помочь разрешить ситуацию, отсюда их исключение из списка «ограниченных функций».

Использование функции аварийной остановки обеспечивает эффективную временную меру для устранения серьезных уязвимостей в вашем смарт-контракте. Однако это увеличивает необходимость для пользователей доверять разработчикам в том, что они не активируют ее в корыстных целях. С этой целью возможными решениями являются децентрализация управления аварийной остановкой путем подчинения ее механизму ончейн-голосования, временной блокировке (timelock) или одобрению с кошелька с мультиподписью.

Мониторинг событий

События (opens in a new tab) позволяют отслеживать вызовы функций смарт-контракта и отслеживать изменения переменных состояния. В идеале следует запрограммировать смарт-контракт на генерацию события всякий раз, когда какая-либо сторона предпринимает критически важное для безопасности действие (например, вывод средств).

Регистрация событий и их мониторинг офчейн дает представление об операциях контракта и способствует более быстрому обнаружению злонамеренных действий. Это означает, что ваша команда может быстрее реагировать на взломы и принимать меры для смягчения последствий для пользователей, такие как приостановка функций или выполнение обновления.

Вы также можете выбрать готовый инструмент мониторинга, который автоматически пересылает оповещения всякий раз, когда кто-то взаимодействует с вашими контрактами. Эти инструменты позволят вам создавать настраиваемые оповещения на основе различных триггеров, таких как объем транзакций, частота вызовов функций или конкретные задействованные функции. Например, вы можете запрограммировать оповещение, которое поступает, когда сумма, выведенная за одну транзакцию, превышает определенный порог.

7. Разработайте безопасные системы управления

Возможно, вы захотите децентрализовать свое приложение, передав контроль над основными смарт-контрактами членам сообщества. В этом случае система смарт-контрактов будет включать модуль управления — механизм, который позволяет членам сообщества одобрять административные действия через систему ончейн-управления. Например, предложение об обновлении прокси-контракта до новой реализации может быть вынесено на голосование держателей токенов.

Децентрализованное управление может быть полезным, особенно потому, что оно согласовывает интересы разработчиков и конечных пользователей. Тем не менее, механизмы управления смарт-контрактами могут создавать новые риски при неправильной реализации. Правдоподобный сценарий — если злоумышленник приобретает огромную силу голоса (измеряемую количеством удерживаемых токенов), взяв флеш-кредит, и продвигает вредоносное предложение.

Один из способов предотвращения проблем, связанных с ончейн-управлением, — использовать временную блокировку (timelock) (opens in a new tab). Временная блокировка не позволяет смарт-контракту выполнять определенные действия до тех пор, пока не пройдет определенное количество времени. Другие стратегии включают назначение «веса голоса» каждому токену в зависимости от того, как долго он был заблокирован, или измерение силы голоса адреса в исторический период (например, 2-3 блока в прошлом) вместо текущего блока. Оба метода снижают вероятность быстрого накопления силы голоса для влияния на ончейн-голосование.

Подробнее о разработке безопасных систем управления (opens in a new tab), различных механизмах голосования в DAO (opens in a new tab) и распространенных векторах атак на DAO с использованием DeFi (opens in a new tab) по предоставленным ссылкам.

8. Сведите сложность кода к минимуму

Традиционные разработчики программного обеспечения знакомы с принципом KISS («делай это проще, глупец»), который не рекомендует вносить ненужную сложность в проектирование программного обеспечения. Это следует из давнего мнения о том, что «сложные системы дают сбои сложными способами» и более подвержены дорогостоящим ошибкам.

Сохранение простоты имеет особое значение при написании смарт-контрактов, учитывая, что смарт-контракты потенциально контролируют большие объемы ценности. Совет для достижения простоты при написании смарт-контрактов — по возможности повторно использовать существующие библиотеки, такие как контракты ОпенЗеппелин (opens in a new tab). Поскольку эти библиотеки были тщательно проверены и протестированы разработчиками, их использование снижает вероятность появления ошибок при написании новой функциональности с нуля.

Еще один распространенный совет — писать небольшие функции и сохранять модульность контрактов, разделяя бизнес-логику между несколькими контрактами. Написание более простого кода не только уменьшает поверхность атаки в смарт-контракте, но и облегчает рассуждения о правильности всей системы и раннее обнаружение возможных ошибок проектирования.

9. Защищайтесь от распространенных уязвимостей смарт-контрактов

Повторный вход

EVM не допускает параллелизма, что означает, что два контракта, участвующие в вызове сообщения, не могут выполняться одновременно. Внешний вызов приостанавливает выполнение и память вызывающего контракта до тех пор, пока вызов не вернется, после чего выполнение продолжается в обычном режиме. Этот процесс можно формально описать как передачу потока управления (opens in a new tab) другому контракту.

Хотя передача потока управления ненадежным контрактам в основном безвредна, она может вызвать проблемы, такие как повторный вход. Атака повторного входа происходит, когда вредоносный контракт вызывает обратно уязвимый контракт до завершения первоначального вызова функции. Этот тип атаки лучше всего объяснить на примере.

Рассмотрим простой смарт-контракт («Жертва»), который позволяет любому вносить и выводить эфир:

Этот контракт предоставляет функцию withdraw(), позволяющую пользователям выводить ETH, ранее внесенные в контракт. При обработке вывода средств контракт выполняет следующие операции:

  1. Проверяет баланс ETH пользователя
  2. Отправляет средства на вызывающий адрес
  3. Сбрасывает их баланс до 0, предотвращая дополнительные выводы средств пользователем

Функция withdraw() в контракте Victim следует шаблону «проверки-взаимодействия-эффекты». Она проверяет, выполняются ли условия, необходимые для выполнения (т. е. у пользователя положительный баланс ETH), и выполняет взаимодействие, отправляя ETH на адрес вызывающего, прежде чем применить эффекты транзакции (т. е. уменьшение баланса пользователя).

Если withdraw() вызывается из внешнего аккаунта (EOA), функция выполняется как ожидалось: msg.sender.call.value() отправляет ETH вызывающему. Однако, если msg.sender является аккаунтом смарт-контракта, вызывающим withdraw(), отправка средств с использованием msg.sender.call.value() также вызовет выполнение кода, хранящегося по этому адресу.

Представьте, что это код, развернутый по адресу контракта:

Этот контракт предназначен для выполнения трех вещей:

  1. Принять депозит с другого аккаунта (вероятно, EOA злоумышленника)
  2. Внести 1 ETH в контракт Жертвы
  3. Вывести 1 ETH, хранящийся в смарт-контракте

Здесь нет ничего плохого, за исключением того, что Attacker имеет другую функцию, которая снова вызывает withdraw() в Victim, если газ, оставшийся от входящего msg.sender.call.value, превышает 40 000. Это дает Attacker возможность повторно войти в Victim и вывести больше средств до завершения первого вызова withdraw. Цикл выглядит так:

Суть в том, что поскольку баланс вызывающего не устанавливается в 0 до завершения выполнения функции, последующие вызовы будут успешными и позволят вызывающему вывести свой баланс несколько раз. Этот вид атаки может быть использован для опустошения средств смарт-контракта, как это произошло при взломе DAO в 2016 году (opens in a new tab). Атаки повторного входа по-прежнему остаются критической проблемой для смарт-контрактов сегодня, как показывают публичные списки эксплойтов повторного входа (opens in a new tab).

Как предотвратить атаки повторного входа

Один из подходов к борьбе с повторным входом — следование шаблону проверки-эффекты-взаимодействия (opens in a new tab). Этот шаблон упорядочивает выполнение функций таким образом, что код, выполняющий необходимые проверки перед продолжением выполнения, идет первым, за ним следует код, который манипулирует состоянием контракта, а код, взаимодействующий с другими контрактами или EOA, идет последним.

Шаблон проверки-эффекты-взаимодействия используется в пересмотренной версии контракта Victim, показанной ниже:

contract NoLongerAVictim {
    function withdraw() external {
        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;
        (bool success, ) = msg.sender.call.value(amount)("");
        require(success);
    }
}

Этот контракт выполняет проверку баланса пользователя, применяет эффекты функции withdraw() (путем сброса баланса пользователя до 0) и переходит к выполнению взаимодействия (отправка ETH на адрес пользователя). Это гарантирует, что контракт обновляет свое хранилище перед внешним вызовом, устраняя условие повторного входа, которое сделало возможной первую атаку. Контракт Attacker все еще может вызвать обратно NoLongerAVictim, но поскольку balances[msg.sender] был установлен в 0, дополнительные выводы средств вызовут ошибку.

Другой вариант — использовать блокировку взаимного исключения (обычно описываемую как «мьютекс»), которая блокирует часть состояния контракта до завершения вызова функции. Это реализуется с использованием логической переменной, которая устанавливается в true перед выполнением функции и возвращается к false после завершения вызова. Как видно из примера ниже, использование мьютекса защищает функцию от рекурсивных вызовов, пока первоначальный вызов все еще обрабатывается, эффективно останавливая повторный вход.

Вы также можете использовать систему платежей по запросу (pull payments) (opens in a new tab), которая требует от пользователей выводить средства из смарт-контрактов, вместо системы «платежей по отправке» (push payments), которая отправляет средства на аккаунты. Это устраняет возможность непреднамеренного запуска кода по неизвестным адресам (а также может предотвратить определенные атаки типа «отказ в обслуживании»).

Целочисленные переполнения (underflow и overflow)

Целочисленное переполнение происходит, когда результаты арифметической операции выходят за пределы допустимого диапазона значений, заставляя его «перевернуться» к наименьшему представимому значению. Например, uint8 может хранить значения только до 2^8-1=255. Арифметические операции, приводящие к значениям выше 255, вызовут переполнение и сбросят uint до 0, подобно тому, как одометр в автомобиле сбрасывается на 0, как только достигает максимального пробега (999999).

Целочисленные антипереполнения (underflows) происходят по аналогичным причинам: результаты арифметической операции падают ниже допустимого диапазона. Скажем, вы попытались уменьшить 0 в uint8, результат просто перевернется к максимальному представимому значению (255).

Как целочисленные переполнения, так и антипереполнения могут привести к неожиданным изменениям переменных состояния контракта и привести к незапланированному выполнению. Ниже приведен пример, показывающий, как злоумышленник может использовать арифметическое переполнение в смарт-контракте для выполнения недопустимой операции:

Как предотвратить целочисленные переполнения и антипереполнения

Начиная с версии 0.8.0, компилятор Solidity отклоняет код, который приводит к целочисленным переполнениям и антипереполнениям. Однако контракты, скомпилированные с более низкой версией компилятора, должны либо выполнять проверки функций, включающих арифметические операции, либо использовать библиотеку (например, SafeMath (opens in a new tab)), которая проверяет наличие переполнения/антипереполнения.

Манипулирование оракулом

Оракулы получают офчейн-информацию и отправляют ее ончейн для использования смарт-контрактами. С помощью оракулов вы можете разрабатывать смарт-контракты, которые взаимодействуют с офчейн-системами, такими как рынки капитала, что значительно расширяет их применение.

Но если оракул поврежден и отправляет неверную информацию ончейн, смарт-контракты будут выполняться на основе ошибочных входных данных, что может вызвать проблемы. Это основа «проблемы оракула», которая касается задачи обеспечения точности, актуальности и своевременности информации от блокчейн-оракула.

Связанная с этим проблема безопасности заключается в использовании ончейн-оракула, такого как децентрализованная биржа, для получения спотовой цены актива. Платформы кредитования в индустрии децентрализованных финансов (DeFi) часто делают это для определения стоимости залога пользователя, чтобы определить, сколько он может занять.

Цены на DEX часто бывают точными, во многом благодаря арбитражерам, восстанавливающим паритет на рынках. Однако они открыты для манипуляций, особенно если ончейн-оракул рассчитывает цены активов на основе исторических моделей торговли (как это обычно бывает).

Например, злоумышленник может искусственно завысить спотовую цену актива, взяв флеш-кредит прямо перед взаимодействием с вашим контрактом кредитования. Запрос цены актива на DEX вернет значение выше нормального (из-за крупного «ордера на покупку» злоумышленника, искажающего спрос на актив), что позволит ему занять больше, чем следовало бы. Такие «атаки с использованием флеш-кредитов» использовались для эксплуатации зависимости от ценовых оракулов среди DeFi-приложений, что стоило протоколам миллионов потерянных средств.

Как предотвратить манипулирование оракулом

Минимальное требование для предотвращения манипулирования оракулом (opens in a new tab) — использовать децентрализованную сеть оракулов, которая запрашивает информацию из нескольких источников, чтобы избежать единых точек отказа. В большинстве случаев децентрализованные оракулы имеют встроенные криптоэкономические стимулы для поощрения узлов оракула сообщать правильную информацию, что делает их более безопасными, чем централизованные оракулы.

Если вы планируете запрашивать цены активов у ончейн-оракула, рассмотрите возможность использования того, который реализует механизм средневзвешенной по времени цены (TWAP). Оракул TWAP (opens in a new tab) запрашивает цену актива в два разных момента времени (которые вы можете изменить) и рассчитывает спотовую цену на основе полученного среднего значения. Выбор более длительных периодов времени защищает ваш протокол от манипулирования ценами, поскольку крупные ордера, выполненные недавно, не могут повлиять на цены активов.

Ресурсы по безопасности смарт-контрактов для разработчиков

Инструменты для анализа смарт-контрактов и проверки правильности кода

  • Инструменты и библиотеки для тестированияКоллекция стандартных для отрасли инструментов и библиотек для проведения модульного тестирования, статического и динамического анализа смарт-контрактов.

  • Инструменты формальной верификацииИнструменты для проверки функциональной правильности смарт-контрактов и проверки инвариантов.

  • Сервисы аудита смарт-контрактовСписок организаций, предоставляющих услуги аудита смарт-контрактов для проектов разработки на Эфириуме.

  • Платформы bug bountyПлатформы для координации программ bug bounty и вознаграждения за ответственное раскрытие критических уязвимостей в смарт-контрактах.

  • Fork Checker (opens in a new tab)Бесплатный онлайн-инструмент для проверки всей доступной информации о форке контракта.

  • ABI Encoder (opens in a new tab)Бесплатный онлайн-сервис для кодирования функций вашего контракта на Solidity и аргументов конструктора.

  • Aderyn (opens in a new tab)Статический анализатор Solidity, который обходит абстрактные синтаксические деревья (AST) для выявления предполагаемых уязвимостей и выводит проблемы в удобном для чтения формате markdown.

Инструменты для мониторинга смарт-контрактов

  • Tenderly Real-Time Alerting (opens in a new tab)Инструмент для получения уведомлений в реальном времени, когда в ваших смарт-контрактах или кошельках происходят необычные или неожиданные события.

Инструменты для безопасного администрирования смарт-контрактов

  • Safe (opens in a new tab)Кошелек на базе смарт-контракта, работающий на Эфириуме, который требует минимального количества людей для того, чтобы одобрить транзакцию перед ее выполнением (M из N).

  • Контракты ОпенЗеппелин (opens in a new tab)Библиотеки контрактов для реализации административных функций, включая владение контрактом, обновления, контроль доступа, управление, возможность приостановки и многое другое.

Сервисы аудита смарт-контрактов

  • ConsenSys Diligence (opens in a new tab)Сервис аудита смарт-контрактов, помогающий проектам во всей экосистеме блокчейна убедиться, что их протоколы готовы к запуску и созданы для защиты пользователей.

  • CertiK (opens in a new tab)Компания по безопасности блокчейна, впервые применившая передовую технологию формальной верификации для смарт-контрактов и блокчейн-сетей.

  • Trail of Bits (opens in a new tab)Компания по кибербезопасности, которая объединяет исследования в области безопасности с мышлением злоумышленника для снижения рисков и укрепления кода.

  • PeckShield (opens in a new tab)Компания по безопасности блокчейна, предлагающая продукты и услуги для обеспечения безопасности, приватности и удобства использования всей экосистемы блокчейна.

  • QuantStamp (opens in a new tab)Сервис аудита, способствующий массовому внедрению технологии блокчейн посредством услуг по оценке безопасности и рисков.

  • ОпенЗеппелин (opens in a new tab)Компания по безопасности смарт-контрактов, предоставляющая аудит безопасности для распределенных систем.

  • Runtime Verification (opens in a new tab)Компания по безопасности, специализирующаяся на формальном моделировании и верификации смарт-контрактов.

  • Hacken (opens in a new tab)Аудитор кибербезопасности Web3, применяющий комплексный подход к безопасности блокчейна.

  • Незермайнд (opens in a new tab)Услуги аудита Solidity и Cairo, обеспечивающие целостность смарт-контрактов и безопасность пользователей в Эфириуме и Starknet.

  • HashEx (opens in a new tab)HashEx специализируется на аудите блокчейна и смарт-контрактов для обеспечения безопасности криптовалют, предоставляя такие услуги, как разработка смарт-контрактов, тестирование на проникновение и консалтинг в сфере блокчейна.

  • Code4rena (opens in a new tab)Платформа соревновательного аудита, которая стимулирует экспертов по безопасности смарт-контрактов находить уязвимости и помогать делать Web3 более безопасным.

  • CodeHawks (opens in a new tab)Платформа соревновательного аудита, проводящая конкурсы по аудиту смарт-контрактов для исследователей безопасности.

  • Cyfrin (opens in a new tab)Ведущая компания в области безопасности Web3, развивающая криптобезопасность с помощью продуктов и услуг по аудиту смарт-контрактов.

  • ImmuneBytes (opens in a new tab)Фирма по безопасности Web3, предлагающая аудит безопасности блокчейн-систем с помощью команды опытных аудиторов и лучших в своем классе инструментов.

  • Oxorio (opens in a new tab)Аудит смарт-контрактов и услуги по безопасности блокчейна с экспертизой в EVM, Solidity, ZK и кроссчейн-технологиях для криптокомпаний и проектов децентрализованных финансов (DeFi).

  • Inference (opens in a new tab)Компания по аудиту безопасности, специализирующаяся на аудите смарт-контрактов для блокчейнов на базе EVM. Благодаря своим опытным аудиторам они выявляют потенциальные проблемы и предлагают практические решения для их устранения перед развертыванием.

Платформы bug bounty

  • Immunefi (opens in a new tab)Платформа bug bounty для смарт-контрактов и DeFi-проектов, где исследователи безопасности проверяют код, раскрывают уязвимости, получают оплату и делают криптовалюту безопаснее.

  • HackerOne (opens in a new tab)Платформа для координации уязвимостей и bug bounty, которая связывает компании с тестировщиками на проникновение и исследователями кибербезопасности.

  • HackenProof (opens in a new tab)Экспертная платформа bug bounty для криптопроектов (DeFi, смарт-контракты, кошельки, CEX и другие), где специалисты по безопасности предоставляют услуги сортировки, а исследователи получают оплату за актуальные и проверенные отчеты об ошибках.

  • Sherlock (opens in a new tab)Андеррайтер в Web3 для безопасности смарт-контрактов, где выплаты аудиторам управляются через смарт-контракты, чтобы гарантировать справедливую оплату за найденные ошибки.

  • CodeHawks (opens in a new tab)Платформа соревновательного bug bounty, где аудиторы принимают участие в конкурсах и испытаниях по безопасности, а (вскоре) и в собственных частных аудитах.

Публикации об известных уязвимостях и эксплойтах смарт-контрактов

  • ConsenSys: Известные атаки на смарт-контракты (opens in a new tab)Понятное для новичков объяснение наиболее значимых уязвимостей контрактов с примерами кода для большинства случаев.

  • Реестр SWC (opens in a new tab)Курируемый список элементов Common Weakness Enumeration (CWE), применимых к смарт-контрактам Эфириума.

  • Rekt (opens in a new tab)Регулярно обновляемое издание о громких взломах и эксплойтах в сфере криптовалют, а также подробные отчеты о разборе инцидентов (post-mortem).

Задачи для изучения безопасности смарт-контрактов

  • Awesome BlockSec CTF (opens in a new tab)Курируемый список варгеймов по безопасности блокчейна, задач и соревнований Capture The Flag (opens in a new tab), а также описаний их решений.

  • Damn Vulnerable DeFi (opens in a new tab)Варгейм для изучения наступательной безопасности смарт-контрактов DeFi и развития навыков поиска ошибок и аудита безопасности.

  • Ethernaut (opens in a new tab)Варгейм на базе Web3/Solidity, где каждый уровень представляет собой смарт-контракт, который нужно «взломать».

  • HackenProof x HackTheBox (opens in a new tab)Задача по взлому смарт-контрактов в формате фэнтезийного приключения. Успешное выполнение задачи также дает доступ к частной программе bug bounty.

Лучшие практики по обеспечению безопасности смарт-контрактов

Руководства по безопасности смарт-контрактов