Ето как се случва един от най-често срещаните хакове за интелигентни договори, които струват милиони на компаниите на Web 3...

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

И така, какво точно представляват атаките за повторно влизане? Как са разположени? И има ли някакви мерки, които разработчиците могат да предприемат, за да предотвратят това?

Какво е атака за повторно влизане?

Атака за повторно влизане възниква, когато уязвима функция на интелигентен договор прави външно повикване към злонамерен договор, като временно се отказва от контрола върху потока на транзакциите. След това злонамереният договор многократно извиква оригиналната функция на интелигентния договор, преди да завърши изпълнението си, докато източва средствата си.

instagram viewer

По същество транзакцията за теглене в блокчейна на Ethereum следва цикъл от три стъпки: потвърждение на баланса, превод и актуализация на баланса. Ако киберпрестъпник може да отвлече цикъла преди актуализацията на баланса, той може многократно да тегли средства, докато портфейлът не бъде източен.

Кредит за изображение: Etherscan

Един от най-скандалните блокчейн хакове, хакването на Ethereum DAO, както е описано в Coindesk, беше атака за повторно влизане, която доведе до загуба на eth на стойност над $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;
}
}

The VulnerableContract позволява на потребителите да депозират eth в договора, като използват депозит функция. След това потребителите могат да изтеглят своите депозирани 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. The AttackerContract взема адреса на VulnerableContract в своя конструктор и го съхранява в vulnerableContract променлива.
  2. The атака функция се извиква от атакуващия, като депозира малко eth в VulnerableContract използвайки депозит функция и след това незабавно извикване на оттегляне функция на VulnerableContract.
  3. The оттегляне функция в VulnerableContract прехвърля исканото количество eth към нападателя AttackerContract преди актуализиране на баланса, но тъй като договорът на нападателя е поставен на пауза по време на външното повикване, функцията все още не е завършена.
  4. The получавам функция в AttackerContract се задейства, защото VulnerableContract изпрати eth към този договор по време на външното повикване.
  5. Функцията за получаване проверява дали AttackerContract балансът е поне 1 етер (сумата за теглене), след което влиза отново в VulnerableContract като го извикате оттегляне функция отново.
  6. Стъпки три до пет повтаряйте, докато VulnerableContract свършват средствата и договорът на нападателя натрупва значително количество eth.
  7. Накрая нападателят може да се обади на тегли откраднати средства функция в AttackerContract да откраднат всички средства, натрупани в договора им.

Атаката може да се случи много бързо, в зависимост от производителността на мрежата. При включването на сложни интелигентни договори като DAO Hack, което доведе до хард форка на Ethereum в Ethereum и Ethereum Classic, атаката продължава няколко часа.

Как да предотвратите атака за повторно влизане

За да предотвратим атака за повторно влизане, трябва да модифицираме уязвимия интелигентен договор, за да следваме най-добрите практики за сигурно разработване на интелигентен договор. В този случай трябва да приложим модела „проверки-ефекти-взаимодействия“, както е в кода по-долу.

// 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[msg.sender]), което показва, че в момента не се извършва друго теглене от същата сметка.

Ако акаунтът не е заключен, договорът продължава с промяна на състоянието и външно взаимодействие. След промяна на състоянието и външно взаимодействие акаунтът се отключва отново, което позволява бъдещи тегления.

Видове атаки за повторно влизане

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

Като цяло има три основни типа атаки за повторно влизане въз основа на тяхното естество на експлоатация.

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

Атаките за повторно влизане могат да се проявят в различни форми и затова изискват специфични мерки за предотвратяване на всяка от тях.

Защита от атаки за повторно влизане

Атаките за повторно влизане са причинили значителни финансови загуби и са подкопали доверието в блокчейн приложенията. За да защитят договорите, разработчиците трябва старателно да възприемат най-добрите практики, за да избегнат уязвимости при повторно влизане.

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