Атаки повторної входу є однією з найвідоміших уразливостей у безпеці смарт-контрактів, відповідальних за мільйони вкрадених коштів на різних блокчейн-протоколах. Ця стаття досліджує механіку, що стоїть за уразливостями повторної входу, і представляє комплексні техніки запобігання, які повинні впроваджувати розробники, що піклуються про безпеку.
Що таке атака повторного входу?
В основі реентраційної атаки лежить ситуація, коли функція в смарт-контракті повторно викликається до завершення її попереднього виконання. Основна вразливість виникає, коли смарт-контракт викликає зовнішній контракт до того, як вирішить свої зміни стану, що створює можливість для експлуатації.
У типовій ситуації Контракт A взаємодіє з Контрактом B, викликаючи одну з його функцій. Критична вразливість безпеки виникає, коли Контракт B отримує можливість знову викликати Контракт A, поки Контракт A все ще виконує свою первинну функцію. Цей рекурсивний патерн взаємодії створює основу для потенційної експлуатації.
Як працюють атаки повторного входу: покроковий розбір
Розгляньте цей сценарій: Контракт A має в загальному 10 ETH, при цьому Контракт B вніс 1 ETH у Контракт A. Уразливість стає експлуатованою, коли Контракт B намагається вивести свої кошти через наступну послідовність:
Контракт B викликає функцію withdraw() в Контракті A
Контракт A перевіряє, що баланс Контракту B більший за 0 (пройшов перевірку)
Контракт A надсилає ETH до Контракту B, що викликає функцію fallback Контракту B
Перш ніж Контракт A може оновити баланс Контракту B до нуля, функція зворотного виклику в Контракті B знову викликає withdraw()
Контракт A перевіряє баланс Контракту B, який все ще показує 1 ETH (не оновлено)
Процес повторюється, поки кошти Контракту A не будуть вичерпані
Ключовою вразливістю є те, що оновлення балансу відбувається після переказу ETH, що дозволяє зловмиснику використовувати один і той же баланс кілька разів, перш ніж його буде встановлено на нуль.
Анатомія атаки: Технічна реалізація
Давайте розглянемо вразливий кодовий патерн:
солідність
// Вразливий контракт EtherStore
контракт EtherStore {
mapping(адреса => uint) публічні баланси;
функція deposit() public payable {
баланси[msg.sender] += msg.value;
}
функція withdrawAll() публічна {
uint bal = balances[msg.sender];
require(bal > 0);
// Вразливість: Зовнішній виклик перед оновленням стану
(bool відправлено, ) = msg.sender.call{value: bal}("");
require(sent, "Не вдалося надіслати Ether");
// Оновлення стану відбувається занадто пізно
баланси[msg.sender] = 0;
}
}
Тепер давайте подивимося, як зловмисник може скористатися цією вразливістю:
Солідність
// Атакуючий контракт, що експлуатує вразливість повторного входу
контракт Attack {
EtherStore загальнодоступний etherStore;
конструктор(адреса _etherStoreAddress) {
etherStore = EtherStore(_etherStoreAddress);
}
// Функція резервного копіювання, яка викликається, коли EtherStore відправляє ефір
receive() зовнішній платний {
if(адрес(ефірСховище).баланс >= 1 ефір) {
// Повторно введіть функцію withdrawAll
etherStore.withdrawAll();
}
}
функція attack() зовнішня платна {
require(msg.value >= 1 ether);
// Депозит для встановлення балансу в EtherStore
etherStore.deposit{значення: 1 ефір}();
// Розпочати процес виведення, активуючи атаку повторного входу
etherStore.withdrawAll();
}
}
Послідовність атаки починається, коли атакуючий викликає attack(). Це вносить 1 ETH для встановлення балансу, а потім викликає withdrawAll(). Коли EtherStore повертає ETH, це викликає функцію receive() в контракті Attack, яка знову викликає withdrawAll() перед тим, як баланс буде оновлено. Цей цикл триває, поки EtherStore не буде вичерпаним.
Комплексні техніки запобігання
Розробники, які дбають про безпеку, можуть реалізувати три надійні техніки для захисту від вразливостей повторного входу:
1. Захист на рівні функції: модифікатор nonReentrant
Найбільш поширеним захистом на рівні індивідуальної функції є реалізація захисту від повторних викликів:
модифікатор nonReentrant() {
require(!заблоковано, "Дзвінок повторного входу");
заблоковано = true;
_;
locked = false;
}
// Застосуйте цей модифікатор до вразливих функцій
функція withdrawFunds() публічна nonReentrant {
// Код функції захищений від повторного входу
}
}
Цей підхід ефективно блокує контракт під час виконання функції, запобігаючи будь-яким рекурсивним викликам, поки функція не завершиться і не розблокує стан.
2. Захист міжфункцій: Шаблон Перевірки-Ефекти-Взаємодії
Цей фундаментальний шаблон безпеки реорганізовує код для усунення вразливостей в кількох функціях:
солідність
// УРАЖЕНА ІМПЛЕМЕНТАЦІЯ
функція withdrawAll() публічна {
uint bal = balances[msg.sender];
require(bal > 0);
(bool відправлено, ) = msg.sender.call{value: bal}("");
require(sent, "Не вдалося надіслати Ефір");
balances[msg.sender] = 0; // Оновлення стану після взаємодії
}
// БЕЗПЕЧНА ІНСТАЛЯЦІЯ
функція withdrawAll() публічна {
uint bal = balances[msg.sender];
require(bal > 0); // Перевірки
balances[msg.sender] = 0; // Ефекти (зміни стану)
(bool відправлено, ) = msg.sender.call{value: bal}(""); Взаємодії
require(відправлено, "Не вдалося надіслати Ether");
}
Дотримуючись патерну Checks-Effects-Interactions, контракт оновлює свій стан перед будь-якими зовнішніми взаємодіями, що забезпечує те, що навіть якщо зловмисник намагатиметься повторно ввійти, він зіткнеться з оновленим станом (нульовий баланс).
3. Захист на рівні проекту: Глобальний захисник повторних викликів
Для складних проектів з кількома взаємодіючими контрактами впровадження глобального захисту від повторного входу забезпечує системний захист:
// Усі контракти в проекті успадковують від GlobalReentrancyGuard
contract SecureContract is GlobalReentrancyGuard {
function vulnerableOperation() public globalNonReentrant {
// Захищено від повторного входу в усіх аспектах проекту
}
}
Цей підхід є особливо цінним для захисту від атак повторного входу між контрактами в DeFi протоколах, де кілька контрактів взаємодіють один з одним.
Реальний вплив вразливостей повторного входу
Вразливості повторного входу призвели до деяких з найбільш руйнівних експлойтів в історії блокчейну. Непрославлений хак DAO 2016 року призвів до викрадення приблизно 3,6 мільйона ETH (, що на той час коштувало близько $50 мільйонів, і в кінцевому підсумку призвів до хард-форку Ethereum, який створив Ethereum Classic.
Нещодавно, у 2020 році, протокол Lendf.Me втратив приблизно ) мільйон через атаку повторного входу, що підкреслює, що незважаючи на підвищену обізнаність, ці вразливості продовжують становити значні ризики для безпеки смарт-контрактів.
Найкращі практики безпеки
Окрім зазначених конкретних технік, розробники повинні дотримуватись цих додаткових практик безпеки:
Завжди використовуйте останню версію Solidity, яка пропонує поліпшені функції безпеки
Предмет контракти підлягають ретельним аудитам безпеки від авторитетних фірм
Впровадьте всебічне покриття тестами включаючи модульні тести для крайніх випадків
Почніть з мінімального впливу ETH при розгортанні нових контрактів у продукцію
Розгляньте формальну верифікацію для контрактів з великою вартістю
Застосовуючи ці захисні техніки та дотримуючись найкращих практик безпеки, розробники можуть суттєво зменшити ризик атак повторного входу та створити більш безпечні смарт-контракти для блокчейн-застосунків.
Ця сторінка може містити контент третіх осіб, який надається виключно в інформаційних цілях (не в якості запевнень/гарантій) і не повинен розглядатися як схвалення його поглядів компанією Gate, а також як фінансова або професійна консультація. Див. Застереження для отримання детальної інформації.
Атаки повторного входу в Смарт-контрактах: Розуміння вразливості та реалізація стратегій запобігання
Атаки повторної входу є однією з найвідоміших уразливостей у безпеці смарт-контрактів, відповідальних за мільйони вкрадених коштів на різних блокчейн-протоколах. Ця стаття досліджує механіку, що стоїть за уразливостями повторної входу, і представляє комплексні техніки запобігання, які повинні впроваджувати розробники, що піклуються про безпеку.
Що таке атака повторного входу?
В основі реентраційної атаки лежить ситуація, коли функція в смарт-контракті повторно викликається до завершення її попереднього виконання. Основна вразливість виникає, коли смарт-контракт викликає зовнішній контракт до того, як вирішить свої зміни стану, що створює можливість для експлуатації.
У типовій ситуації Контракт A взаємодіє з Контрактом B, викликаючи одну з його функцій. Критична вразливість безпеки виникає, коли Контракт B отримує можливість знову викликати Контракт A, поки Контракт A все ще виконує свою первинну функцію. Цей рекурсивний патерн взаємодії створює основу для потенційної експлуатації.
Як працюють атаки повторного входу: покроковий розбір
Розгляньте цей сценарій: Контракт A має в загальному 10 ETH, при цьому Контракт B вніс 1 ETH у Контракт A. Уразливість стає експлуатованою, коли Контракт B намагається вивести свої кошти через наступну послідовність:
Ключовою вразливістю є те, що оновлення балансу відбувається після переказу ETH, що дозволяє зловмиснику використовувати один і той же баланс кілька разів, перш ніж його буде встановлено на нуль.
Анатомія атаки: Технічна реалізація
Давайте розглянемо вразливий кодовий патерн:
солідність // Вразливий контракт EtherStore контракт EtherStore { mapping(адреса => uint) публічні баланси;
}
Тепер давайте подивимося, як зловмисник може скористатися цією вразливістю:
Солідність // Атакуючий контракт, що експлуатує вразливість повторного входу контракт Attack { EtherStore загальнодоступний etherStore;
}
Послідовність атаки починається, коли атакуючий викликає attack(). Це вносить 1 ETH для встановлення балансу, а потім викликає withdrawAll(). Коли EtherStore повертає ETH, це викликає функцію receive() в контракті Attack, яка знову викликає withdrawAll() перед тим, як баланс буде оновлено. Цей цикл триває, поки EtherStore не буде вичерпаним.
Комплексні техніки запобігання
Розробники, які дбають про безпеку, можуть реалізувати три надійні техніки для захисту від вразливостей повторного входу:
1. Захист на рівні функції: модифікатор nonReentrant
Найбільш поширеним захистом на рівні індивідуальної функції є реалізація захисту від повторних викликів:
солідність contract ReentrancyGuard { bool private locked = false;
}
Цей підхід ефективно блокує контракт під час виконання функції, запобігаючи будь-яким рекурсивним викликам, поки функція не завершиться і не розблокує стан.
2. Захист міжфункцій: Шаблон Перевірки-Ефекти-Взаємодії
Цей фундаментальний шаблон безпеки реорганізовує код для усунення вразливостей в кількох функціях:
солідність // УРАЖЕНА ІМПЛЕМЕНТАЦІЯ функція withdrawAll() публічна { uint bal = balances[msg.sender]; require(bal > 0);
}
// БЕЗПЕЧНА ІНСТАЛЯЦІЯ функція withdrawAll() публічна { uint bal = balances[msg.sender]; require(bal > 0); // Перевірки
}
Дотримуючись патерну Checks-Effects-Interactions, контракт оновлює свій стан перед будь-якими зовнішніми взаємодіями, що забезпечує те, що навіть якщо зловмисник намагатиметься повторно ввійти, він зіткнеться з оновленим станом (нульовий баланс).
3. Захист на рівні проекту: Глобальний захисник повторних викликів
Для складних проектів з кількома взаємодіючими контрактами впровадження глобального захисту від повторного входу забезпечує системний захист:
Солідність contract GlobalReentrancyGuard { bool private _notEntered = true;
}
// Усі контракти в проекті успадковують від GlobalReentrancyGuard contract SecureContract is GlobalReentrancyGuard { function vulnerableOperation() public globalNonReentrant { // Захищено від повторного входу в усіх аспектах проекту } }
Цей підхід є особливо цінним для захисту від атак повторного входу між контрактами в DeFi протоколах, де кілька контрактів взаємодіють один з одним.
Реальний вплив вразливостей повторного входу
Вразливості повторного входу призвели до деяких з найбільш руйнівних експлойтів в історії блокчейну. Непрославлений хак DAO 2016 року призвів до викрадення приблизно 3,6 мільйона ETH (, що на той час коштувало близько $50 мільйонів, і в кінцевому підсумку призвів до хард-форку Ethereum, який створив Ethereum Classic.
Нещодавно, у 2020 році, протокол Lendf.Me втратив приблизно ) мільйон через атаку повторного входу, що підкреслює, що незважаючи на підвищену обізнаність, ці вразливості продовжують становити значні ризики для безпеки смарт-контрактів.
Найкращі практики безпеки
Окрім зазначених конкретних технік, розробники повинні дотримуватись цих додаткових практик безпеки:
Застосовуючи ці захисні техніки та дотримуючись найкращих практик безпеки, розробники можуть суттєво зменшити ризик атак повторного входу та створити більш безпечні смарт-контракти для блокчейн-застосунків.