Ajută la actualizarea acestei pagini

🌏

Există o nouă versiune a acestei pagini, dar acum este doar în engleză. Ajută-ne să traducem cea mai recentă versiune.

Această pagină este incompletă. Dacă ești expert în acest subiect, te rugăm să editezi această pagină și s-o presari cu înțelepciune.

Securitate

Ultima editare: , Invalid DateTime
Edit page

Contractele inteligente Ethereum sunt extrem de flexibile, capabile atât să dețină cantități mari de tokenuri (de multe ori mai mari de 1 miliard USD), cât și să ruleze o logică imuabilă pe baza codului de contract inteligent desfășurat anterior. Deși acest lucru a creat un ecosistem vibrant și creativ de contracte inteligente fără încredere, interconectate, este de asemenea, ecosistemul perfect pentru a atrage atacatorii care doresc să profite prin exploatarea vulnerabilității din contractele inteligente și a comportamentului neașteptat în Ethereum. Codul de contract inteligent, de obicei nu poate fi schimbat ca să fie reparate defectele de securitate, activele care au fost furate din contracte inteligente sunt irecuperabile, iar activele furate sunt extrem de dificil de urmărit. Suma totală a valorii furate sau pierdute din cauza problemelor de contract inteligent este cu ușurință de 1 miliard de dolari. Unele dintre cele mai mari pierderi din cauza erorilor de codare a contractelor inteligente includ:

Condiții prealabile

Aceasta va acoperi securitatea contractelor inteligente, de aceea asigură-te că ești familiarizat cu contractele inteligente înainte de a aborda securitatea.

Cum să scrii coduri de contracte inteligente mai sigure

Înainte de a lansa orice cod pe rețeaua principală, este important să iei suficiente măsuri de precauție pentru a proteja lucrurile de valoare încredințate contractului inteligent. În acest articol, vom discuta câteva atacuri specifice, îți vom oferi resurse pentru a afla mai multe despre diferite tipuri de atacuri și îți vom lăsa câteva instrumente de bază și cele mai bune practici pentru a te asigura de funcționarea corectă a contractelor tale.

Auditurile nu sunt o soluție perfectă

Cu ani înainte, instrumentele pentru scrierea, compilarea, testarea și implementarea contractelor inteligente erau foarte imature, permițând multor proiecte să scrie codul Solidity în moduri întâmplătoare, să-l arunce peste „gard” către un auditor care ar investiga codul pentru a se asigura că acesta funcționează în siguranță și cum era de așteptat. În 2020, procesele de dezvoltare și instrumentele care sprijină scrierea Solidity sunt semnificativ mai bune; valorificarea acestor bune practici nu numai că asigură că proiectul tău este mai ușor de gestionat, ci este o parte vitală a securității lui. Un audit la sfârșitul redactării contractului tău inteligent nu mai este suficient ca singurul aspect de securitate pe care îl ia proiectul tău. Securitatea începe înainte de a scrie prima linie de cod de contract inteligent, securitatea începe cu procese de proiectare și dezvoltare corespunzătoare.

Procesul de dezvoltare al contractelor Inteligente

Cel puțin:

  • Toate codurile stocate într-un sistem de control al versiunii, cum ar fi Git
  • Toate modificările de cod efectuate prin Solicitări Pull
  • Toate Solicitările Pull au cel puțin un examinator. Dacă ești într-un proiect solo, ia în considerare găsirea unui alt autor solo și negociază cu el recenzii de coduri!
  • O singură comandă compilează, implementează și rulează o suită de teste împotriva codului tău utilizând un mediu Ethereum de dezvoltare (vezi: Truffle)
  • Ai rulat codul prin instrumente de analiză a codului de bază, cum ar fi Mythril și Slither, în mod ideal înainte ca fiecare solicitare de extragere să fie îmbinată, comparând diferențele de ieșire
  • Solidity nu emite NICIUN avertisment al compilatorului
  • Codul tău este bine documentat

Sunt mult mai multe de spus despre procesul de dezvoltare, dar aceste elemente sunt un loc bun de început. Pentru mai multe elemente și explicații detaliate, consultă lista de verificare a calității procesului furnizată de DeFiSafety. DefiSafety este un serviciu public neoficial care publică diverse recenzii despre mari aplicații dapp Ethereun publice. O parte a sistemului de evaluare DeFiSafety include cât de bine aderă proiectul la această listă de verificare a calității procesului. Urmând aceste procese:

  • Vei produce un cod mai sigur, prin teste automate, reproductibile
  • Vei permite revizuirea proiectului mai eficient de către auditori
  • Vei integra mai ușor noi programatori
  • Vei permite programatorilor să itereze rapid, să testeze și să obțină feedback despre modificări
  • Vei avea mai puține șanse ca proiectul să aibă regresii

Atacuri și vulnerabilități

După ce ai aflat cum să scrii cod Solidity folosind un proces de dezvoltare eficient, să analizăm câteva vulnerabilități comune Solidity pentru a vedea ce nu poate să meargă bine.

Re-intrare

Re-intrarea este una dintre cele mai mari și mai semnificative probleme de securitate de luat în considerare atunci când se dezvoltă contracte inteligente. Deși EVM nu poate executa mai multe contracte simultan, un contract care apelează un alt contract, întrerupe executarea contractului de apelare și starea memoriei până când apelul revine, moment în care executarea se desfășoară în mod normal. Această întrerupere și re-pornire poate crea o vulnerabilitate cunoscută sub numele de „re-intrare”.

Aici este o versiune simplă a unui contract care este vulnerabil la re-intrare:

1// ACEST CONTRACT ARE VULNERABILITATE INTENȚIONATĂ, NU COPIA
2contract Victim {
3 mapping (address => uint256) public balances;
4
5 function deposit() external payable {
6 balances[msg.sender] += msg.value;
7 }
8
9 function withdraw() external {
10 uint256 amount = balances[msg.sender];
11 (bool success, ) = msg.sender.call.value(amount)("");
12 require(success);
13 balances[msg.sender] = 0;
14 }
15}
16
Afișează tot
📋 Copiere

Pentru a permite unui utilizator să retragă ETH-ul stocat anterior în contract, această funcție

  1. Citește valoarea soldului unui utilizator
  2. Le trimite valoarea soldului în ETH
  3. Resetează soldul la 0, deci nu își mai pot retrage din nou soldul.

Dacă este apelată dintr-un cont obișnuit (cum ar fi propriul tău cont Metamask), acest lucru funcționează așa cum te aștepți: msg.sender.call.value() pur și simplu trimite contul ETH. Cu toate acestea, contractele inteligente pot efectua și ele apeluri. Dacă un contract personalizat, rău intenționat este cel care apelează withdraw(), msg.sender.call.value() nu numai că va trimite amount din ETH, va apela implicit și contractul pentru a începe executarea codului. Imaginează-ți acest contract răuvoitor:

1contract Attacker {
2 function beginAttack() external payable {
3 Victim(VICTIM_ADDRESS).deposit.value(1 ether)();
4 Victim(VICTIM_ADDRESS).withdraw();
5 }
6
7 function() external payable {
8 if (gasleft() > 40000) {
9 Victim(VICTIM_ADDRESS).withdraw();
10 }
11 }
12}
13
Afișează tot
📋 Copiere

Apelarea Attacker.beginAttack() va începe un ciclu care arată ceva de genul:

10.) Atacatorul EOA apelează Attacker.beginAttack() cu 1 ETH
20.) Attacker.beginAttack() depozitează 1 ETH în Victimă
3
4 1.) Atacant -> Victim.withdraw()
5 1.) Victima citește balanceOf[msg.sender]
6 1.) Victima trimite ETH Atacantului (care execută funcția implicită)
7 2.) Atacant -> Victim.withdraw()
8 2.) Victima citește balanceOf[msg.sender]
9 2.) Victima trimite ETH Atacantului (care execută funcția implicită)
10 3.) Atacant -> Victim.withdraw()
11 3.) Victima citește balanceOf[msg.sender]
12 3.) Victima trimite ETH Atacantului (care execută funcția implicită)
13 4.) Atacantul nu mai are suficient gaz, se întoarce fără să apeleze din nou
14 3.) balances[msg.sender] = 0;
15 2.) balances[msg.sender] = 0; (a fost deja 0)
16 1.) balances[msg.sender] = 0; (a fost deja 0)
17
Afișează tot

Apelul Attacker.beginAttack cu 1 ETH va ataca prin re-intrare Victima, retrăgând mai mult ETH decât a furnizat (luat din soldurile altor utilizatori, cauzând contractul Victimă să devină sub-garantat)

Cum să te descurci cu re-intrarea (modul greșit)

Ai putea lua în calcul să învingi re-intrarea împiedicând orice contract inteligent să interacționeze cu codul tău. Când cauți stackoverflow, găsești acest fragment de code (snippet) cu tone de voturi pozitive:

1function isContract(address addr) internal returns (bool) {
2 uint size;
3 assembly { size := extcodesize(addr) }
4 return size > 0;
5}
6
📋 Copiere

Pare să aibă sens: contractele au cod, dacă apelantul are orice cod, nu-i permite să depună. Hai să-l adăugăm:

1// ACEST CONTRACT ARE VULNERABILITATE INTENȚIONATĂ, NU COPIA
2contract ContractCheckVictim {
3 mapping (address => uint256) public balances;
4
5 function isContract(address addr) internal returns (bool) {
6 uint size;
7 assembly { size := extcodesize(addr) }
8 return size > 0;
9 }
10
11 function deposit() external payable {
12 require(!isContract(msg.sender)); // <- LINIE NOUĂ
13 balances[msg.sender] += msg.value;
14 }
15
16 function withdraw() external {
17 uint256 amount = balances[msg.sender];
18 (bool success, ) = msg.sender.call.value(amount)("");
19 require(success);
20 balances[msg.sender] = 0;
21 }
22}
23
Afișează tot
📋 Copiere

Acum, pentru a depune ETH, nu trebuie să ai codul de contract inteligent în adresa ta. Totuși, acest lucru este ușor de învins cu următorul contract Atacator:

1contract ContractCheckAttacker {
2 constructor() public payable {
3 ContractCheckVictim(VICTIM_ADDRESS).deposit(1 ether); // <- Linie nouă
4 }
5
6 function beginAttack() external payable {
7 ContractCheckVictim(VICTIM_ADDRESS).withdraw();
8 }
9
10 function() external payable {
11 if (gasleft() > 40000) {
12 Victim(VICTIM_ADDRESS).withdraw();
13 }
14 }
15}
16
Afișează tot
📋 Copiere

În timp ce primul atac a fost un atac asupra logicii contractelor, acesta este un atac asupra comportamentului de implementare a contractului Ethereum. În timpul construcției, un contract nu a returnat codul său încă pentru a fi implementat la adresa sa, dar păstrează controlul complet EVM ÎN TIMPUL acestui proces.

Din punct de vedere tehnic, este posibil să împiedici contractele inteligente să-ți apeleze codul, utilizând această linie:

1require(tx.origin == msg.sender)
2
📋 Copiere

Totuși, aceasta nu este încă o soluție bună. Unul dintre cele mai interesante aspecte ale Ethereum este combinabilitatea: contractele inteligente se integrează și se construiesc unul pe celălalt. Prin utilizarea liniei de mai sus, limitezi utilitatea proiectului.

Cum să te descurci cu re-intrarea (modul corect)

Prin simpla comutare a ordinii actualizării de stocare și a apelului extern, prevenim condiția de re-intrare care a permis atacul. Apelarea înapoi la „withdraw” (retragere), cât timp este încă posibil, nu va avantaja atacantul, deoarece soldul stocării va fi deja setat la 0.

1contract NoLongerAVictim {
2 function withdraw() external {
3 uint256 amount = balances[msg.sender];
4 balances[msg.sender] = 0;
5 (bool success, ) = msg.sender.call.value(amount)("");
6 require(success);
7 }
8}
9
📋 Copiere

Codul de mai sus urmează modelul de design „Verificări-Efecte-Interacțiuni”, care ajută la protejarea împotriva re-intrării. Poți citi mai multe despre Verificări-Efecte-Interacțiuni aici

Cum să te descurci cu re-intrarea (opțiunea nucleară)

De fiecare dată când trimiți ETH la o adresă care nu este de încredere sau interacționezi cu un contract necunoscut (cum ar fi apelarea transfer() a unei adrese de token furnizate de utilizator), te deschizi singur la posibilitatea re-intrării. Proiectând contracte care nu trimit nici ETH și nici nu apelează contracte nesigure, împiedici posibilitatea re-intrării!

Mai multe tipuri de atac

Tipurile de atac de mai sus acoperă probleme de codificare a contractelor inteligente (re-intrare) și ciudățenii Ethereum (executarea codului în interiorul constructorilor de contracte, înainte ca acest cod să fie disponibil la adresa contractului). Există multe, multe alte tipuri de atac de care trebuie să știi, cum ar fi:

  • Front-running
  • Refuzul de a trimite ETH
  • Overflow/underflow de numere întregi

Referințe suplimentare:

Instrumente de securitate

Deși nu există nici un substitut pentru înțelegerea elementelor de bază ale securității Ethereum și angajarea unei firme de audit profesionale pentru a-ți examina codul, există multe instrumente disponibile pentru a evidenția probleme potențiale cu codul tău.

Securitatea contractelor inteligente

Slither - cadru de analiză statică a Solidity scris în Python 3.

MythX - API de analiză de securitate pentru contracte inteligente Ethereum.

Mythril - instrument de analiză de securitate pentru bytecode EVM.

SmartContract.Codes - motor de căutare pentru coduri sursă Solidity verificate.

Manticore - o interfață tip linie de comandă care utilizează un instrument de execuție simbolică pe contracte inteligente și binare.

Securify - scaner de securitate pentru contracte inteligente Ethereum

ERC20 Verifier - un instrument de verificare folosit pentru a controla conformitatea unui contract cu standardul ERC20A.

Verificare formală

Informații despre verificarea formală

Folosirea instrumentelor

Două dintre cele mai populare instrumente pentru analiza securității contractelor inteligente sunt:

Ambele sunt instrumente utile care analizează codul tău și raportează probleme. Fiecare are o versiune [commercial] găzduită, dar și o versiune gratuită pentru a rula local. Următorul este un exemplu rapid de cum să rulezi Slither, care este disponibil într-o imagine Docker convenabilă trailofbits/eth-security-toolbox. Va trebui să instalezi Docker-ul dacă nu l-ai instalat deja.

$ mkdir test-slither
$ curl https://gist.githubusercontent.com/epheph/460e6ff4f02c4ac582794a41e1f103bf/raw/9e761af793d4414c39370f063a46a3f71686b579/gistfile1.txt > bad-contract.sol
$ docker run -v `pwd`:/share -it --rm trailofbits/eth-security-toolbox
docker$ cd /share
docker$ solc-select 0.5.11
docker$ slither bad-contract.sol

Va genera această ieșire:

ethsec@1435b241ca60:/share$ slither bad-contract.sol
INFO:Detectors:
Reentrancy in Victim.withdraw() (bad-contract.sol#11-16):
External calls:
- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)
State variables written after the call(s):
- balances[msg.sender] = 0 (bad-contract.sol#15)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities
INFO:Detectors:
Low level call in Victim.withdraw() (bad-contract.sol#11-16):
- (success) = msg.sender.call.value(amount)() (bad-contract.sol#13)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls
INFO:Slither:bad-contract.sol analyzed (1 contracts with 46 detectors), 2 result(s) found
INFO:Slither:Use https://crytic.io/ to get access to additional detectors and Github integration
Afișează tot

Slither a identificat potențialul de re-intrare aici, identificând liniile cheie în care ar putea apare problema, oferindu-ne un link pentru mai multe detalii despre problemă:

Referință: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities

permițându-ți să afli rapid despre probleme potențiale cu codul. Ca toate instrumentele de testare automată, Slither nu este perfect și greșește, raportând prea mult. Poate avertiza despre o potențială re-intrare, chiar și atunci când nu există o vulnerabilitate exploatabilă. Adesea, revizuirea DIFERENȚEI modificării codului în ieșirile Slither, este extrem de folositoare, ajutând la descoperirea de vulnerabilități care au fost introduse mult mai devreme decât să aștepți până când proiectul tău este complet codificat.

Referințe suplimentare

Ghid de practici bune în materie de securitate a contractelor inteligente

Standard de verificare a securității contractelor inteligente (SCSVS)

Cunoști o resursă comunitară care te-a ajutat? Editează această pagină și adaug-o!