Швидка версія: Рекурсія - це як якщо хакер знову телефонує вам, поки ви ще передаєте їм гроші — вони висмоктують ваш гаманець ще до того, як транзакція закінчиться.
Ось жорстка правда: понад $100M було втрачено через експлойти повторного входу. Найвідоміший? Хак DAO (2016) викрав $50M в ETH, експлуатуючи цю саму вразливість.
Як працює атака (Використовуючи реальну логіку коду)
Уявіть, що ContractA має 10 ETH, а ContractB має баланс у 1 ETH, збережений всередині нього.
Коли ContractB викликає withdrawAll(), ось що повинно статися:
Перевірте баланс > 0 ✓
Відправити ETH назад ✓
Оновити баланс до 0 ✓
Але ось де це ламається: Зловмисник експлуатує порядок виконання.
Потік експлуатації:
Зловмисник викликає attack() → що викликає withdrawAll() на ContractA
ContractA надсилає 1 ETH і викликає функцію резервного копіювання атакуючого ()
Перед оновленням балансу до 0, fallback() негайно знову викликає withdrawAll()
ContractA перевіряє: “Чи баланс > 0?” ТАК (, тому що він ще не оновлений!)
Відправляє ще 1 ETH → знову викликає fallback()
Цей цикл триває, поки ContractA повністю не буде вичерпано
Ключове спостереження: Оновлення балансу відбувається ПІСЛЯ переказу ETH. Це вікно вразливості.
Три стратегії захисту
1. Модифікатор nonReentrant (Одинарний захист функції)
Заблокувати функцію під час її виконання. Повторний вхід не дозволено:
солідність
модифікатор nonReentrant {
require(!locked, “Немає повторного входу”);
заблоковано = true;
_;
locked = false;
}
Ефекти: Оновити стан (баланс = 0) ← Перемістіть це перед відправкою ETH
Взаємодії: Відправити ETH
Неправильне замовлення:
require(баланс > 0);
→ відправити ETH
→ баланс = 0; // Занадто пізно!
Правильний порядок:
require(balance > 0);
→ баланс = 0; // Оновити ПЕРШИЙ
→ відправити ETH // Потім взаємодіяти
Тепер, навіть якщо fallback() повторно входить, баланс вже 0. Атака не вдається.
3. GlobalReentrancyGuard (Захист міжконтракту)
Для складних систем з кількома взаємодіючими контрактами використовуйте централізований охоронний контракт, який відстежує стан блокування в усіх контрактах. Коли ContractA викликає ContractB, охоронець це фіксує - якщо ContractB намагається знову викликати систему перед поверненням, охоронець блокує це.
Чому це важливо
Рекурсія — це не лише проблема Solidity — це проблема дизайну. Щоразу, коли ви надсилаєте ETH або викликаєте зовнішні функції, ви передаєте контроль ненадійному коду. Функція резервного копіювання зловмисника виконується в контексті ВАШОГО контракту.
Дані: Chainalysis виявила, що ~60% високовартісних експлойтів у 2023-2024 роках були пов'язані з повторними входами або подібними патернами. Провідні проекти, такі як Yearn, Curve та Balancer, мали проблеми з повторними входами.
Основний висновок: Використовуйте Checks-Effects-Interactions за замовчуванням. Додавайте nonReentrant там, де це необхідно. Для багатоконтрактних систем реалізуйте GlobalReentrancyGuard. Втрачені $100M+ могли б бути збережені завдяки цим базовим шаблонам.
Слідкуйте за @TheBlockChainer для більш глибоких досліджень безпеки Web3.
Ця сторінка може містити контент третіх осіб, який надається виключно в інформаційних цілях (не в якості запевнень/гарантій) і не повинен розглядатися як схвалення його поглядів компанією Gate, а також як фінансова або професійна консультація. Див. Застереження для отримання детальної інформації.
Атака повторного входу: Чому Смарт-контракти продовжують бути знищеними ( І як це зупинити )
Швидка версія: Рекурсія - це як якщо хакер знову телефонує вам, поки ви ще передаєте їм гроші — вони висмоктують ваш гаманець ще до того, як транзакція закінчиться.
Ось жорстка правда: понад $100M було втрачено через експлойти повторного входу. Найвідоміший? Хак DAO (2016) викрав $50M в ETH, експлуатуючи цю саму вразливість.
Як працює атака (Використовуючи реальну логіку коду)
Уявіть, що ContractA має 10 ETH, а ContractB має баланс у 1 ETH, збережений всередині нього.
Коли ContractB викликає withdrawAll(), ось що повинно статися:
Але ось де це ламається: Зловмисник експлуатує порядок виконання.
Потік експлуатації:
Ключове спостереження: Оновлення балансу відбувається ПІСЛЯ переказу ETH. Це вікно вразливості.
Три стратегії захисту
1. Модифікатор nonReentrant (Одинарний захист функції)
Заблокувати функцію під час її виконання. Повторний вхід не дозволено: солідність модифікатор nonReentrant { require(!locked, “Немає повторного входу”); заблоковано = true; _; locked = false; }
Простий, але захищає лише одну функцію за раз.
2. Шаблон Перевірок-Ефектів-Взаємодій (Багатофункціональний Захист)
Це зміна гри:
Неправильне замовлення:
require(баланс > 0); → відправити ETH → баланс = 0; // Занадто пізно!
Правильний порядок:
require(balance > 0); → баланс = 0; // Оновити ПЕРШИЙ → відправити ETH // Потім взаємодіяти
Тепер, навіть якщо fallback() повторно входить, баланс вже 0. Атака не вдається.
3. GlobalReentrancyGuard (Захист міжконтракту)
Для складних систем з кількома взаємодіючими контрактами використовуйте централізований охоронний контракт, який відстежує стан блокування в усіх контрактах. Коли ContractA викликає ContractB, охоронець це фіксує - якщо ContractB намагається знову викликати систему перед поверненням, охоронець блокує це.
Чому це важливо
Рекурсія — це не лише проблема Solidity — це проблема дизайну. Щоразу, коли ви надсилаєте ETH або викликаєте зовнішні функції, ви передаєте контроль ненадійному коду. Функція резервного копіювання зловмисника виконується в контексті ВАШОГО контракту.
Дані: Chainalysis виявила, що ~60% високовартісних експлойтів у 2023-2024 роках були пов'язані з повторними входами або подібними патернами. Провідні проекти, такі як Yearn, Curve та Balancer, мали проблеми з повторними входами.
Основний висновок: Використовуйте Checks-Effects-Interactions за замовчуванням. Додавайте nonReentrant там, де це необхідно. Для багатоконтрактних систем реалізуйте GlobalReentrancyGuard. Втрачені $100M+ могли б бути збережені завдяки цим базовим шаблонам.
Слідкуйте за @TheBlockChainer для більш глибоких досліджень безпеки Web3.