Pirmkārt, es palīdzēšu jums vienkāršā veidā saprast, kas ir atkārtotas uzbrukuma uzbrukums un kā jūs varat to novērst, un pēc tam es iedziļināšos kodu piemēros, lai parādītu, kur ir ievainojamības, kāds būtu uzbrucēja kods. un pats galvenais, es jums parādīšu jaunākās pārbaudītās metodes, lai aizsargātu ne tikai vienu, bet arī visus viedos līgumus jūsu projektā.
Spoileris: Ja esat jau dzirdējis par nonReentrant() modifikatoru, turpiniet lasīt, jo jūs gatavojaties atklāt dažas rindiņas zem globalNonReentrant() modifikatora un pārbaužu-efektu-mijiedarbības modeli.
Iepriekš redzamajā attēlā mums ir līgumsA un līgumsB. Tagad, kā jūs zināt, viedais līgums var mijiedarboties ar citu viedo līgumu, piemēram, šajā gadījumā ContractA var izsaukt ContractB. Tātad atkārtotas ienākšanas pamatideja ir tāda, ka ContractB var atzvanīt ContractA, kamēr ContractA joprojām tiek izpildīts.
Tātad, kā uzbrucējs to var izmantot?
Iepriekš ir ContractA, kurā ir 10 ēteri, un mēs redzam, ka ContractB līgumā A ir saglabājis 1 ēteri. Šādā gadījumā ContractB varēs izmantot izņemšanas funkciju no ContractA un nosūtīt Ether atpakaļ sev, kad tas izturēs pārbaudi, ja tā atlikums ir lielāks par 0, lai pēc tam kopējais atlikums tiktu mainīts uz 0.
Tagad redzēsim, kā ContractB var izmantot atkārtotu ieeju, lai izmantotu izņemšanas funkciju un nozagtu visus ēterus no ContractA. Būtībā uzbrucējam būs nepieciešamas divas funkcijas: attack() un fallback().
Programmā Solidity atkāpšanās funkcija ir ārēja funkcija bez nosaukuma, parametriem vai atgriešanas vērtībām. Ikviens var izsaukt atkāpšanās funkciju: izsaucot funkciju, kas līgumā nepastāv; Funkcijas izsaukšana, nenododot nepieciešamos datus; Ētera nosūtīšana bez jebkādiem datiem uz līgumu.
Reentrancy darbojas (soli pa solim sekosim bultiņām), kad uzbrucējs izsauc funkciju attack() kas iekšienē izsauc funkciju attack() no ContractA. Funkcijā tas pārbaudīs, vai līguma B atlikums ir lielāks par 0, un, ja tā, tas turpinās izpildi.
Tā kā ContractB atlikums ir lielāks par 0, tas nosūta šo 1 ēteru atpakaļ un aktivizē atkāpšanās funkciju. Ņemiet vērā, ka šobrīd ContractA ir 9 ēteri, bet ContractB jau ir 1 ēteris.
Pēc tam, kad tiek izpildīta atkāpšanās funkcija, tā atkal aktivizē līguma A izņemšanas funkciju, vēlreiz pārbaudot, vai līguma B atlikums ir lielāks par 0. Ja vēlreiz pārbaudīsit iepriekš redzamo attēlu, pamanīsit, ka tā atlikums joprojām ir 1 ēteris.
Tas nozīmē, ka pārbaude tiek izturēta un līgums B nosūta citu ēteri, kas aktivizē atkāpšanās funkciju. Ņemiet vērā, ka tā kā rinda, kurā mums ir “bilance=0”, nekad netiek izpildīta, tas turpināsies, līdz būs pazudis viss Ether no ContractA.
___________
Tagad apskatīsim viedo līgumu, kurā mēs varam identificēt atkārtotu ienākšanu ar Solidity kodu.
EtherStore līgumā mums ir funkcija deposit(), kas saglabā un atjaunina sūtītāja atlikumus, un pēc tam funkcija atsaukt() , kas vienlaikus uzņems visus saglabātos atlikumus. Lūdzu, ņemiet vērā, ka tiek ieviests atvērts All(), kur tas vispirms pārbauda, vai atlikums ir lielāks par 0, un tūlīt pēc ētera nosūtīšanas, atkal atstājot sūtītāja atlikuma atjaunināšanu uz 0.
Šeit mums ir līgums Attack, kas izmantos atkārtotu ieeju, lai iztukšotu EtherStore līgumu. Analizēsim tā kodu:
Savā konstruktorā uzbrucējs nodos EtherStore adresi, lai izveidotu instanci un tādējādi varētu izmantot tā funkcijas.
Šeit mēs redzam atkāpšanās () funkciju, kas tiks izsaukta, kad EtherStore nosūtīs Ether šim līgumam. Tajā būs zvana izņemšana no EtherStore, ja atlikums būs vienāds ar 1 vai lielāks.
Un funkcijā Attack() mums ir loģika, kas izmantos EtherStore. Kā redzam, vispirms mēs uzsāksim uzbrukumu, pārliecinoties, ka mums ir pietiekami daudz ētera, pēc tam noguldīsim 1 ēteri, lai EtherStore atlikums būtu lielāks par 0, un tādējādi izietu pārbaudes pirms izņemšanas.
Iepriekš ContractA un ContractB piemēros soli pa solim paskaidroju, kā kods darbosies, tāpēc tagad izveidosim kopsavilkumu par to, kā tas būs. Vispirms uzbrucējs izsauks Attack(), kas iekšienē izsauks returnAll() no EtherStore, kas pēc tam nosūtīs Ether to Attack līguma atkāpšanās funkciju. Un tur tas sāks atgriešanos un iztukšo EtherStore līdzsvaru.
Tātad, kā mēs varam aizsargāt savus līgumus no atkārtotas ienākšanas uzbrukumiem?
Es jums parādīšu trīs profilakses paņēmienus, lai tos pilnībā aizsargātu. Es runāšu par to, kā novērst atkārtotu ienākšanu vienā funkcijā, atkārtotas ievadīšanas savstarpēju funkciju un atkārtotu ieiešanas savstarpēju līgumu.
Pirmais paņēmiens vienas funkcijas aizsardzībai ir pārveidotāja izmantošana noReentrant.
Modifikators ir īpaša veida funkcija, ko izmantojat, lai mainītu citu funkciju darbību. Modifikatori ļauj funkcijai pievienot papildu nosacījumus vai funkcionalitāti, nepārrakstot visu funkciju.
Šeit mēs bloķējam līgumu, kamēr funkcija tiek izpildīta. Tādā veidā tas nevarēs atkārtoti ievadīt vienu funkciju, jo tai būs jāiet cauri funkcijas kodam un pēc tam jāmaina bloķētā stāvokļa mainīgais uz false, lai vēlreiz izturētu prasībā veikto pārbaudi.
___________
Otrais paņēmiens ir pārbaužu-efektu-mijiedarbības modeļa izmantošana, kas pasargās mūsu līgumus no vairāku funkciju atkārtotas ienākšanas. Vai atjauninātajā EtherStore līgumā varat redzēt, kas ir mainījies?
Lai dziļāk izpētītu Check-Effects-Interaction modeli, iesaku izlasīt https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html
Iepriekš redzams salīdzinājums starp ievainojamo kodu no attēla kreisajā pusē, kur atlikums tika atjaunināts pēc ētera nosūtīšanas, ko, kā redzams iepriekš, iespējams, nekad nevarēja sasniegt, un labajā pusē ir veikta atlikumu pārvietošana[ msg.sender] = 0 (vai efekts) tieši pēc require (bal > 0) (pārbaude), bet pirms ētera nosūtīšanas (mijiedarbība).
Tādā veidā mēs nodrošināsim, ka pat tad, ja cita funkcija piekļūst withdrawAll(), šis līgums tiks aizsargāts no uzbrucēja, jo atlikums vienmēr tiks atjaunināts pirms ētera nosūtīšanas.
Šablonu izveidoja https://twitter.com/GMX_IO
Trešais paņēmiens, ko es jums parādīšu, ir GlobalReentrancyGuard līguma izveidošana, lai pasargātu no atkārtotas iestāšanās līgumos. Ir svarīgi saprast, ka tas attiecas uz projektiem ar vairākiem līgumiem, kas savstarpēji mijiedarbojas.
Ideja šeit ir tāda pati kā noReentrant modifikatorā, kuru es izskaidroju pirmajā tehnikā, tas ievada modifikatoru, atjaunina mainīgo, lai bloķētu līgumu, un tas neatbloķē to, kamēr tas nepabeidz kodu. Šeit galvenā atšķirība ir tāda, ka mēs izmantojam mainīgo, kas saglabāts atsevišķā līgumā, kas tiek izmantots kā vieta, kur pārbaudīt, vai funkcija ir ievadīta vai nē.
Šeit esmu izveidojis piemēru bez faktiskā koda un tikai ar funkciju nosaukumiem, lai saprastu ideju, jo, pēc manas pieredzes, tas var palīdzēt vizualizēt situāciju vairāk nekā tikai rakstot to ar vārdiem.
Šeit uzbrucējs izsauktu funkciju ScheduledTransfer līgumā, kas pēc nosacījumu izpildes nosūtīs norādīto Ether uz AttackTransfer līgumu, kas tādējādi ievadītu rezerves funkciju un tādējādi “atceltu” darījumu no ScheduledTransfer līguma punkta. apskatīt un tomēr saņemt Ēteri. Un šādā veidā tas sāks meklēt, līdz tiks iztukšoti visi ēteri no ScheduledTransfer.
Izmantojot iepriekš minēto GlobalReentrancyGuard, tas ļaus izvairīties no šāda uzbrukuma scenārija.
__________________
Twitter @TheBlockChainer, lai atrastu vairāk ikdienas atjauninājumu par viedajiem līgumiem, Web3 drošību, stabilitāti, viedo līgumu auditēšanu un daudz ko citu.
__________________