Атаки повторного входу (reentrancy) залишаються однією з найвідоміших і найстійкіших загроз безпеці смарт-контрактів на Ethereum і сумісних з EVM мережах. Незважаючи на роки галузевих зусиль, нові протоколи досі страждають від вразливостей повторного входу, що призводить до значних фінансових втрат — інколи на мільйони доларів. Такі експлойти часто трапляються у DeFi кредитуванні, стекингу та мостаках, де звичайними є зовнішні виклики та трансфери токенів.
У цій статті ми розбираємо механіку атаки повторного входу, визначаємо типові вразливі шаблони контрактів і ділимось перевіреними стратегіями захисту. Спираючись на наш аналіз понад 255 аудитів смарт-контрактів у Soken, ми демонструємо, як тонкі практики програмування Solidity та комплексні робочі процеси тестування можуть запобігти загрозам повторного входу. Також ми розглядаємо суміжні ризики, такі як неправильне використання delegatecall та тактики з flash loan, щоб надати повне розуміння, необхідне для будь-якого серйозного Web3-розробника чи аудитора безпеки.
Що таке атака повторного входу і як вона експлуатує смарт-контракти?
Атака повторного входу виникає, коли шкідливий контракт багаторазово викликає функцію вразливого контракту до того, як початкове виконання завершиться, експлуатуючи неконсистентний стан контракту.
На практиці вразливість повторного входу виникає через неправильно впорядковані зміни стану та зовнішні виклики всередині контракту. Якщо контракт передає ETH або токени через call або transfer на недовірену адресу, той одержувач може рекурсивно повторно увійти у вразливу функцію до оновлення змінних стану. Це дає можливість зловмиснику вивести кошти або непередбачено змінити логіку контракту.
Наприклад, відомий DAO hack у 2016 році призвів до викрадення близько $60 млн через уразливості повторного входу — це попереджувальна історія. З того часу індустрія зрозуміла, що корінь проблеми — оновлення балансу або стану контракту відбувається лише після зовнішніх викликів, створюючи вікно неконсистентного стану, яке атакують.
Експертна думка з аудитів Soken: «У понад 40% наших останніх аудитів DeFi безпеки зустрічаються патерни повторного входу у тій чи іншій формі, часто у вигляді тонких варіацій із проксі-контрактами або вкладеними викликами. Ідентифікація цих потребує глибокого аналізу міжконтрактних викликів, що виходить за межі простого інспектування одного контракту.»
Типові Solidity шаблони, які призводять до уразливостей повторного входу
Уразливості повторного входу зазвичай походять із конкретної послідовності операцій: зовнішні виклики (наприклад, трансфери ETH або токенів) відбуваються до того, як контракт оновлює свої внутрішні змінні стану.
Найуразливіший шаблон виглядає так:
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount;
}
Тут зовнішній виклик через call.value() відбувається до віднімання балансу користувача. Під час цього виклику fallback-функція зловмисника може рекурсивно викликати withdraw(), виводячи кошти до оновлення балансу.
Ключові ризикові конструкції:
- Зовнішні виклики перед оновленнями стану: Відправлення ETH або токенів до оновлення балансу.
- Використання
callабоsendбез заходів безпеки: Зовнішній виклик через низькорівневі функції уникає перевірок і може активувати fallback-функції. - Відсутність захисту від повторного входу: Немає м’ютекса чи інших блокувань.
- Складні ланцюжки викликів через delegatecall: Вони опосередковано відкривають повторний вхід через зовнішні виклики з викликаних контрактів.
Натомість безпечніший шаблон змінює порядок:
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // оновлення стану спершу
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
Як запобігти атакам повторного входу в Solidity: найкращі практики та інструменти
Найдієвіший захист від повторного входу — оновлювати стан перед зовнішніми викликами разом із використанням явних шаблонів захисту від повторного входу.
Стандартні методи зменшення ризику:
-
Патерн Checks-Effects-Interactions:
Завжди оновлюйте внутрішній стан перед зовнішніми викликами. Це мінімізує можливість неконсистентного стану під час зовнішніх викликів. -
Захист від повторного входу / м’ютекси:
ReentrancyGuardз OpenZeppelin використовує простий м’ютекс для блокування вкладених викликів. Інтегруйте його так:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureContract is ReentrancyGuard {
mapping(address => uint256) balances;
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
}
-
Обмежуйте зовнішні виклики:
Мінімізуйте трансфери чи взаємодії у критичних функціях — використовуйте патерн Pull замість Push, де користувачі самостійно знімають кошти. -
Статичний аналіз і автоматизовані інструменти:
Такі інструменти, як Slither та власні фреймворки аудиту Soken, позначають ризики повторного входу. -
Використовуйте актуальні версії Solidity і функції:
Solidity 0.8+ має вбудовані перевірки переповнення, але ризики повторного входу лишаються, тому поєднуйте безпечний код із перевіреними патернами.
Методологія Soken: Наші аудити поєднують ручне моделювання загроз, символьне виконання й fuzz-тестування для виявлення складних сценаріїв повторного входу, які часто пропускають загальні сканери.
Реальні випадки атак повторного входу та їхній вплив
Атаки повторного входу досі поширені навіть при зростаючій обізнаності. Нижче наведено порівняння відомих хаків, що ілюструють різноманітність та вартість таких експлойтів:
| Інцидент | Дата | Сума збитку | Використаний патерн | Головний урок |
|---|---|---|---|---|
| The DAO Hack | 2016-06 | ~$60 million | Повторний вхід у splitDAO | Оновлювати стан перед зовнішнім викликом |
| The Lendf.Me Hack | 2020-04 | $25 million+ | Flash loan, що посилює reentrancy | Поєднання захисту від повторного входу й стійкості до flash loan |
| Euler Finance Hack | 2023-03 | $197 million | Вкладений повторний вхід + delegatecall | Складні ланцюжки викликів підвищують ризик |
| Останній хак DeFi Bridge | 2025-11 | $15 million | Експлойт повторного входу при трансфері токенів | Аудит міжконтрактних взаємодій |
Примітка: В аудитах Soken ми особливо наголошуємо на багаторівневій безпеці, особливо навколо міжконтрактних викликів і стандартів токенів, які можуть викликати fallback-функції динамічно.
Delegatecall та вектори flash loan: посилення ризиків повторного входу
Delegatecall, який виконує код у контексті контракту-виклику, може ускладнювати оцінку ризику повторного входу та збільшувати вразливість, особливо у поєднанні з flash loans.
Атаки повторного входу часто використовують flash loans, щоб максимізувати капітал для рекурсивного виведення:
- Flash loans дають велику миттєву ліквідність в одному транзакційному блоці.
- Зловмисник використовує flash-loaned кошти, щоб активувати рекурсивні виклики для зливу ліквідності протоколу до повернення кредиту.
- Використання delegatecall може випадково запускати недовірений зовнішній код, що змінює стан контракту або дозволяє повторний вхід.
Думка з безпеки: «За досвідом Soken, захист від повторного входу виходить за межі окремих контрактів — це питання дизайну протоколу загалом, особливо коли delegatecall і flash loans співіснують. Контракти повинні поєднувати захист від повторного входу з валідацією точок входу і специфічними перевірками для flash loans (ліміти суми кредиту, обмеження на рекурсивні виклики).»
Порівняльна таблиця: популярні техніки запобігання повторному входу
| Техніка | Опис | Переваги | Недоліки | Використання |
|---|---|---|---|---|
| Checks-Effects-Interactions | Оновлювати стан перед зовнішніми викликами | Простий, ефективний | Вимагає дисципліни | Базова практика безпеки |
| Reentrancy Guard (м’ютекс) | Використання Solidity-модифікаторів для запобігання повтору | Блокує рекурсивні виклики явно | Додає невеликий оверхед газу | Рекомендовано для усіх функцій зняття |
| Pull Payment Pattern | Дозволяє користувачам самостійно знімати кошти | Мінімізує ризики автоматичних трансферів | Потребує взаємодії користувача | Ідеально для ескроу та стекингу |
| Статичний та динамічний аналіз | Автоматизовані та ручні аудити коду | Рання виявленість вразливостей | Може пропустити складні міжконтрактні сценарії | Необхідно для впевненості перед деплоєм |
| Перевірки Flash Loan & Delegatecall | Валідація контексту виклику та транзакції | Запобігає посиленню рецидивів через кредити | Складна до впровадження | Критично для DeFi-протоколів з кредитами |
Приклад коду Solidity: уразливий контракт і його виправлення
Ось мінімальний уразливий контракт та його захищена версія з використанням reentrancy guard:
// Уразливий: виведення до оновлення балансу
contract Vulnerable {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient funds");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount; // Уразливість: оновлення після зовнішнього виклику
}
}
Виправлено з OpenZeppelin ReentrancyGuard та додержанням патерну effects before interaction:
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Secure is ReentrancyGuard {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient funds");
balances[msg.sender] -= amount; // Оновлення стану перед зовнішнім викликом
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Порада експерта: Завжди поєднуйте checks-effects-interactions із захистом від повторного входу. Аудит графів викликів контрактів та тести з fuzzing допомагають виявити тонкі багатоступеневі уразливості до релізу.
Уразливості повторного входу залишаються критичною загрозою для смарт-контрактів, особливо у складних DeFi-екосистемах з flash loans та delegatecall. Найефективніший захист поєднує дисципліну коду (checks-effects-interactions), явні захисні механізми, ретельний аудит і контекстно-залежну валідацію транзакцій.
У Soken наше дослідження безпеки поєднує ручні та автоматизовані техніки, відточені на базі 282+ аудитів смарт-контрактів, що постійно виявляють і усувають ризики повторного входу й суміжні проблеми. Використання передових практик і ґрунтовних методологій тестування є обов’язковим для кожного проєкту, що працює з коштами користувачів.
Потрібні професійні поради з безпеки? Команда аудитів Soken перевірила понад 255 смарт-контрактів і захистила протоколи з вартістю понад $2 млрд. Чи потрібен вам комплексний аудит, безкоштовна X-Ray оцінка безпеки або допомога в навігації за крипторегуляціями, ми готові допомогти.
Зв’язатися з експертом Soken | Переглянути наші звіти з аудитів