Перейти до основного вмісту
Change page

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

Смарт-контракти надзвичайно гнучкі та здатні контролювати великі обсяги цінностей і даних, виконуючи незмінну логіку на основі коду, розгорнутого в блокчейні. Це створило динамічну екосистему бездовірчих та децентралізованих застосунків, які надають багато переваг порівняно зі застарілими системами. Вони також створюють можливості для зловмисників, які прагнуть отримати прибуток, використовуючи вразливості в смарт-контрактах.

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

Хоча цифри різняться, за оцінками, загальна сума цінностей, вкрадених або втрачених через дефекти безпеки в смарт-контрактах, легко перевищує 1 мільярд доларів. Сюди входять такі гучні інциденти, як злам 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 bounties)

Створення програми винагород за виявлення помилок (bug bounty) — це ще один підхід до реалізації зовнішніх перевірок коду. Винагорода за виявлення помилок — це фінансова винагорода, яка надається особам (зазвичай «білим» хакерам), що виявляють вразливості в застосунку.

При правильному використанні програми винагород за виявлення помилок дають членам хакерської спільноти стимул перевіряти ваш код на наявність критичних недоліків. Реальним прикладом є «помилка нескінченних грошей», яка дозволила б зловмиснику створити необмежену кількість етеру в Optimism (opens in a new tab), протоколі рівня 2 (l2), що працює на Етеріумі. На щастя, «білий» хакер виявив недолік (opens in a new tab) і повідомив команду, отримавши при цьому велику виплату (opens in a new tab).

Корисна стратегія полягає у встановленні виплати за програмою винагород за виявлення помилок пропорційно до суми коштів, що знаходяться під загрозою. Описаний як «масштабована винагорода за виявлення помилок (opens in a new tab)», цей підхід надає фінансові стимули для осіб відповідально розкривати вразливості замість того, щоб експлуатувати їх.

5. Дотримуйтесь найкращих практик під час розробки смарт-контрактів

Існування аудитів та програм винагород за виявлення помилок не звільняє вас від відповідальності за написання високоякісного коду. Хороша безпека смарт-контрактів починається з дотримання належних процесів проєктування та розробки:

  • Зберігайте весь код у системі контролю версій, такій як git

  • Вносьте всі зміни до коду через pull-запити

  • Переконайтеся, що pull-запити мають принаймні одного незалежного рецензента — якщо ви працюєте над проєктом самостійно, подумайте про те, щоб знайти інших розробників і обмінюватися перевірками коду

  • Використовуйте середовище розробки для тестування, компіляції та розгортання смарт-контрактів

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

  • Переконайтеся, що ваш код компілюється без помилок, а компілятор 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 («роби це простіше, дурнику»), який радить не вводити непотрібну складність у дизайн програмного забезпечення. Це випливає з давньої думки про те, що «складні системи дають збої складними способами» і більш схильні до дорогих помилок.

Збереження простоти має особливе значення при написанні смарт-контрактів, враховуючи, що смарт-контракти потенційно контролюють великі обсяги цінностей. Порада для досягнення простоти при написанні смарт-контрактів полягає в повторному використанні існуючих бібліотек, таких як ОупенЗеппелін Contracts (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), яка надсилає кошти на акаунти. Це усуває можливість ненавмисного запуску коду за невідомими адресами (а також може запобігти певним атакам типу «відмова в обслуговуванні»).

Недостачі та переповнення цілих чисел

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

Недостачі цілих чисел трапляються з подібних причин: результати арифметичної операції падають нижче допустимого діапазону. Скажімо, ви спробували зменшити 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 - Платформи для координації програм винагород за знайдені помилки та винагородження за відповідальне розкриття критичних вразливостей у смарт-контрактах.

  • 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) - Курований список військових ігор (wargames) з безпеки блокчейну, завдань та змагань Capture The Flag (opens in a new tab), а також описів їх рішень.

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

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

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

Найкращі практики для захисту смарт-контрактів

Посібники з безпеки смарт-контрактів