blogul 1NewsDevelopersEnterpriseBlockchain Explained Evenimente și conferințe ApăsațiBuletine informative

Aboneaza-te la newsletter-ul nostru.

Adresa de email

Vă respectăm confidențialitatea

AcasăBlogDezvoltare blockchain

Recomandări privind securitatea contractelor inteligente Ethereum

De la modul de gestionare a apelurilor externe la schemele de angajament, iată 10+ modele de securitate a contractelor inteligente de urmat atunci când vă bazați pe Ethereum. De ConsenSys 10 iulie 2020 Postat pe 10 iulie 2020

Recomandări privind securitatea contractelor inteligente Ethereum

Așa cum am tratat în Smart Contract Security Mindset, un dezvoltator vigilent Ethereum păstrează întotdeauna cinci principii de top:

  • Pregătește-te pentru eșec
  • Lansați cu atenție
  • Păstrați contractele simple
  • Sa fii la curent
  • Fiți conștienți de idiosincrasia EVM

În această postare, ne vom scufunda în idiosincraziile EVM și vom parcurge o listă de tipare pe care ar trebui să le urmați atunci când dezvoltați orice sistem de contract inteligent pe Ethereum. Această piesă este în primul rând pentru dezvoltatorii intermediari Ethereum. Dacă sunteți încă în primele etape ale explorării, consultați programul de dezvoltare blockchain la cerere al Academiei ConsenSys. 

Bine, hai să ne scufundăm.

Apeluri externe

Aveți grijă când efectuați apeluri externe

Apelurile la contracte inteligente de încredere pot introduce mai multe riscuri sau erori neașteptate. Apelurile externe pot executa coduri rău intenționate în acel contract sau în orice alt contract de care depinde. Prin urmare, tratați fiecare apel extern ca pe un potențial risc de securitate. Când nu este posibil sau nedorit să eliminați apelurile externe, utilizați recomandările din restul acestei secțiuni pentru a minimiza pericolul.

Marcați contractele de încredere

Atunci când interacționați cu contracte externe, denumiți-vă variabilele, metodele și interfețele contractuale într-un mod care face clar că interacțiunea cu acestea este potențial nesigură. Acest lucru se aplică propriilor funcții care apelează contracte externe.

// Bad Bank.withdraw (100); // Nu este clar dacă funcția de încredere sau de încredere makeWithdrawal (sumă uint) {// Nu este clar că această funcție este potențial nesigură Bank.withdraw (sumă); } // bun UntrustedBank.withdraw (100); // apel extern de încredere TrustedBank.withdraw (100); // contract bancar extern dar de încredere menținut de funcția XYZ Corp makeUntrustedWithdrawal (sumă uint) {UntrustedBank.withdraw (sumă); } Limbaj cod: PHP (php)

Evitați modificările de stare după apeluri externe

Indiferent dacă utilizați apeluri brute (de forma someAddress.call ()) sau apeluri contractuale (de forma ExternalContract.someMethod ()), presupuneți că s-ar putea executa codul rău intenționat. Chiar dacă ExternalContract nu este rău intenționat, codul rău intenționat poate fi executat prin orice contracte pe care le apelează.

Un pericol special este că codul rău intenționat poate deturna fluxul de control, ducând la vulnerabilități datorate reintrării. (Vedea Reîncadrare pentru o discuție mai completă a acestei probleme).

Dacă efectuați un apel către un contract extern de încredere, evitați modificările de stare după apel. Acest tipar este, de asemenea, numit uneori tipar verificări-efecte-interacțiuni.

Vedea SWC-107

Nu utilizați transfer () sau trimitere ().

.transfer () și. trimite () transmite exact 2.300 de gaz către destinatar. Scopul acestei subvenții de gaze codificate a fost prevenirea vulnerabilități de reintrare, dar acest lucru are sens doar în ipoteza că costurile cu gazele sunt constante. EIP 1884, care făcea parte din furca dură din Istanbul, a crescut costul gazului operațiunii SLOAD. Acest lucru a făcut ca funcția de rezervă a unui contract să coste mai mult de 2300 de gaze. Vă recomandăm să nu mai utilizați.transfer () și.send () și, în schimb, să utilizați.call ().

// contract defect Vulnerabil {retragere funcție (sumă uint256) extern {// Aceasta transmite 2300 de gaze, ceea ce s-ar putea să nu fie suficient dacă destinatarul // este un contract și costurile cu gazele se schimbă. msg.sender.transfer (suma); }} // contract bun Fix {funcție de retragere (suma uint256) externă {// Aceasta transmite toate gazele disponibile. Asigurați-vă că verificați valoarea returnată! (succes bool,) = msg.sender.call.value (sumă) (""); cere (succes, "Transferul nu a reușit."); }} Limba codului: JavaScript (javascript)

Rețineți că.call () nu face nimic pentru a atenua atacurile de reintrare, așa că trebuie luate alte măsuri de precauție. Pentru a preveni atacurile de reintrare, utilizați tipar verificări-efecte-interacțiuni.

Gestionarea erorilor în apelurile externe

Solidity oferă metode de apel la nivel scăzut care funcționează pe adrese brute: address.call (), address.callcode (), address.delegatecall () și address.send (). Aceste metode de nivel scăzut nu aruncă niciodată o excepție, dar vor reveni false dacă apelul întâlnește o excepție. Pe de altă parte, apelurile contractuale (de exemplu, ExternalContract.doSomething ()) vor propaga automat o aruncare (de exemplu, ExternalContract.doSomething () va arunca și dacă aruncă doSomething ()).

Dacă alegeți să utilizați metodele de apel de nivel scăzut, asigurați-vă că gestionați posibilitatea ca apelul să eșueze, verificând valoarea returnată.

// rău someAddress.send (55); someAddress.call.value (55) (""); // acest lucru este dublu periculos, deoarece va redirecționa tot gazul rămas și nu verifică rezultatul someAddress.call.value (100) (bytes4 (sha3 ("depozit()"))); // dacă depunerea aruncă o excepție, apelul brut () va returna doar falsul și tranzacția NU va fi anulată // bine (succes bool,) = someAddress.call.value (55) (""); if (! success) {// handle code failure} ExternalContract (someAddress) .deposit.value (100) (); Limbajul codului: JavaScript (javascript)

Vedea SWC-104

Favorizarea push pull over pentru apeluri externe

Apelurile externe pot eșua accidental sau deliberat. Pentru a minimiza daunele cauzate de astfel de eșecuri, este adesea mai bine să izolați fiecare apel extern în propria tranzacție care poate fi inițiată de destinatarul apelului. Acest lucru este relevant mai ales pentru plăți, unde este mai bine să lăsați utilizatorii să retragă fonduri, decât să împingă fondurile către ei automat. (Acest lucru reduce, de asemenea, șansa de probleme cu limita de gaz.) Evitați combinarea mai multor transferuri de eter într-o singură tranzacție.

// licitație de contract rău {adresă cel mai înalt ofertant; uint maximumBid; function bid () payable {require (msg.value >= maximumBid); if (maximumBidder! = address (0)) {(succes bool,) = maximumBidder.call.value (maximumBid) (""); cere (succes); // dacă acest apel eșuează în mod constant, nimeni altcineva nu poate licita} maximumBidder = msg.sender; maximumBid = msg.value; }} // licitație contractuală bună {adresa cea mai înaltă ofertă; uint maximumBid; mapare (adresa => uint) rambursări; function bid () de plătit extern {require (msg.value >= maximumBid); if (maximumBidder! = address (0)) {refunds [maximumBidder] + = maximumBidder; // înregistrați rambursarea pe care acest utilizator o poate solicita} maximumBidder = msg.sender; maximumBid = msg.value; } function retireRefund () external {uint refund = refunds [msg.sender]; restituiri [msg.sender] = 0; (succes bool,) = msg.sender.call.value (rambursare) (""); cere (succes); }} Limba codului: JavaScript (javascript)

Vedea SWC-128

Nu delegați apelul la codul care nu este de încredere

Funcția de apelare delegat apelează funcții din alte contracte ca și când ar aparține contractului apelantului. Astfel, apelatul poate schimba starea adresei de apel. Acest lucru poate fi nesigur. Un exemplu de mai jos arată cum utilizarea delegatecall poate duce la distrugerea contractului și pierderea echilibrului acestuia.

contract Destructor {funcția doWork () external {selfdestruct (0); }} contract Worker {funcția doWork (address _internalWorker) public {// unsafe _internalWorker.delegatecall (bytes4 (keccak256 ("munceste()"))); }} Limba codului: JavaScript (javascript)

Dacă Worker.doWork () este apelat cu adresa contractului de destructor implementat ca argument, contractul de lucrător se va autodistruge. Delegați execuția numai contractelor de încredere și niciodată către adresa furnizată de un utilizator.

Avertizare

Nu presupuneți că contractele sunt create cu sold zero. Un atacator poate trimite eter la adresa unui contract înainte de a fi creat. Contractele nu ar trebui să presupună că starea sa inițială conține un sold zero. Vedea numărul 61 pentru mai multe detalii.

Vedea SWC-112

Amintiți-vă că eterul poate fi trimis cu forța la un cont

Feriți-vă de codificarea unui invariant care verifică strict soldul unui contract.

Un atacator poate trimite forțat eter în orice cont. Acest lucru nu poate fi prevenit (nici măcar cu o funcție de rezervă care face o revenire ()).

Atacatorul poate face acest lucru creând un contract, finanțându-l cu 1 wei și invocând autodistrugerea (victimAddress). Niciun cod nu este invocat în victimAddress, deci nu poate fi prevenit. Acest lucru este valabil și pentru recompensa blocului care este trimisă la adresa minerului, care poate fi orice adresă arbitrară.

De asemenea, deoarece adresele contractului pot fi precomputate, eterul poate fi trimis la o adresă înainte ca contractul să fie implementat.

Vedea SWC-132

Amintiți-vă că datele din lanț sunt publice

Multe aplicații necesită ca datele trimise să fie private până la un moment dat pentru a funcționa. Jocuri (de exemplu, foarfece de hârtie pe lanț) și mecanisme de licitație (de exemplu, licitație sigilată Licitații Vickrey) sunt două mari categorii de exemple. Dacă creați o aplicație în care confidențialitatea este o problemă, asigurați-vă că evitați să solicitați utilizatorilor să publice informații prea devreme. Cea mai bună strategie este de a utiliza scheme de angajament cu faze separate: comiteți mai întâi folosind hash-ul valorilor și într-o fază ulterioară dezvăluind valorile.

Exemple:

  • În foarfecele de hârtie rock, trebuie ca ambii jucători să trimită mai întâi un hash al mișcării lor intenționate, apoi să solicite ambilor jucători să-și trimită mutarea; dacă mișcarea trimisă nu se potrivește cu hashul, aruncați-o.
  • Într-o licitație, solicitați jucătorilor să trimită un hash din valoarea ofertei lor într-o fază inițială (împreună cu un depozit mai mare decât valoarea ofertei lor), iar apoi să trimită valoarea licitației în a doua fază.
  • Atunci când dezvoltați o aplicație care depinde de un generator de numere aleatorii, ordinea ar trebui să fie întotdeauna (1) jucătorii trimit mutări, (2) numărul aleator generat, (3) jucătorii plătiți. Mulți oameni cercetează activ generatorii de numere aleatorii; cele mai bune soluții actuale includ anteturile blocului Bitcoin (verificate prin http://btcrelay.org), schemele hash-commit-revel (de exemplu, o parte generează un număr, își publică hash-ul pentru a „comite” valoarea și apoi dezvăluie valoarea mai târziu) și RANDAO. Deoarece Ethereum este un protocol determinist, nu puteți utiliza nicio variabilă din protocol ca număr aleatoriu imprevizibil. Rețineți, de asemenea, că minerii controlează într-o oarecare măsură valoarea block.blockhash ()*.

Feriți-vă de posibilitatea ca unii participanți să „cadă offline” și să nu revină

Nu faceți ca procesele de rambursare sau de revendicare să depindă de o anumită parte care efectuează o anumită acțiune fără nicio altă modalitate de a scoate fondurile. De exemplu, într-un joc rock-paper-foarfece, o greșeală obișnuită este să nu faci o plată până când ambii jucători nu își depun mișcările; totuși, un jucător rău intenționat îl poate „mâhni” pe celălalt pur și simplu nu își depune niciodată mișcarea – de fapt, dacă un jucător vede mișcarea dezvăluită a celuilalt jucător și determină că a pierdut, nu are niciun motiv să-și prezinte deloc propria mutare. Această problemă poate apărea și în contextul soluționării canalelor de stat. Atunci când astfel de situații sunt o problemă, (1) oferă o modalitate de a ocoli participanții neparticipanți, poate printr-un termen limită, și (2) ia în considerare adăugarea unui stimulent economic suplimentar pentru ca participanții să trimită informații în toate situațiile în care se află ar trebui să o facă.

Feriți-vă de negarea celui mai negativ întreg semnat

Soliditatea oferă mai multe tipuri pentru a lucra cu numere întregi semnate. Ca în majoritatea limbajelor de programare, în Solidity un număr întreg semnat cu N biți poate reprezenta valori de la -2 ^ (N-1) la 2 ^ (N-1) -1. Aceasta înseamnă că nu există un echivalent pozitiv pentru MIN_INT. Negarea este implementată ca găsirea complementului unui număr al celor doi, deci negarea celui mai negativ număr va rezulta în același număr. Acest lucru este valabil pentru toate tipurile de întregi semnate din Solidity (int8, int16, …, int256).

contract Negation {function negate8 (int8 _i) public pure returnează (int8) {return -_i; } function negate16 (int16 _i) public pure returnează (int16) {return -_i; } int8 public a = negate8 (-128); // -128 int16 public b = negate16 (-128); // 128 int16 public c = negate16 (-32768); // -32768} Limbaj cod: PHP (php)

O modalitate de a gestiona acest lucru este să verificați valoarea unei variabile înainte de negare și să aruncați dacă este egală cu MIN_INT. O altă opțiune este să vă asigurați că cel mai negativ număr nu va fi atins niciodată prin utilizarea unui tip cu o capacitate mai mare (de ex. Int32 în loc de int16).

O problemă similară cu tipurile de int apare atunci când MIN_INT este înmulțit sau împărțit la -1.

Codul dvs. blockchain este sigur? 

Sperăm că aceste recomandări au fost utile. Dacă dumneavoastră și echipa dvs. vă pregătiți pentru lansare sau chiar la începutul ciclului de viață al dezvoltării și aveți nevoie de contracte inteligente verificate de sănătate, vă rugăm să nu ezitați să contactați echipa noastră de ingineri de securitate de la ConsenSys Diligence. Suntem aici pentru a vă ajuta să lansați și să vă mențineți aplicațiile Ethereum cu încredere de 100%. 

Rezervați o verificare a punctelor de securitate

Rezervați o recenzie de o zi cu echipa noastră de experți în securitate blockchain. Rezervați-vă astăzi Securitate Contracte inteligente Buletin informativ Abonați-vă la newsletter-ul nostru pentru cele mai recente știri Ethereum, soluții pentru întreprinderi, resurse pentru dezvoltatori și multe altele. Adresa de e-mail Conținut exclusivCum să construiți un produs Blockchain de succesWebinar

Cum să construiți un produs Blockchain de succes

Cum se configurează și se execută un nod EthereumWebinar

Cum se configurează și se execută un nod Ethereum

Cum să vă construiți propriul API EthereumWebinar

Cum să vă construiți propriul API Ethereum

Cum să creați un simbol socialWebinar

Cum să creați un simbol social

Utilizarea instrumentelor de securitate în dezvoltarea contractelor inteligenteWebinar

Utilizarea instrumentelor de securitate în dezvoltarea contractelor inteligente

Viitorul activelor digitale și al DeFi-ului financiarWebinar

Viitorul finanțelor: active digitale și DeFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me