Bine ați venit la ghidul pentru începători pentru auditarea inteligentă a contractelor! Una dintre cele mai bune modalități de a începe cu auditarea contractelor inteligente este să te uiți la câteva tipuri comune de vulnerabilități în contractele inteligente.
Ar fi util dacă aveți deja o înțelegere de bază a limbajului de programare Solidity al Ethereum. După cum ne vom uita la unele dintre codurile scrise de programatorii Noob Solidity.
Atacul de reintrare
Scenariu din lumea reală:
Imaginează-ți că ai 50 de bomboane de ciocolată. Ai o surioară obraznică căreia i-ai permis să ia doar 2 bomboane de ciocolată de la tine la un moment dat. De asemenea, nu vrei să-i oferi mai mult de 10 bomboane de ciocolată într-o zi, de teamă că va avea carii. Pentru a vă asigura acest lucru, în fiecare seară numărați câte ciocolate au mai rămas la voi. Crezi că asta ar funcționa? Sau ai fi spart de sora ta mai mică?
Din păcate, nu va funcționa! Sora ta își dă seama că nu știi câte ciocolate ai până se face seară. Așa că chiar a doua zi, sora ta mai mică te vizitează de 6 ori înainte de seară și ia 2 bomboane de ciocolată de fiecare dată! Acesta este ceea ce numim un atac de reintrare.
Aici actualizezi numărul de ciocolate pe care le ai seara, în loc să actualizezi numărul de fiecare dată când sora ta ia 2 ciocolate de la tine. Acest lucru se întâmplă și cu smart contract. Contractul inteligent presupune un anumit echilibru, în timp ce atacatorul este de fapt ocupat să retragă o anumită cantitate de criptomonede din contract de mai multe ori.
Exemplu de cod din lumea reală:
Acest cod aparține unui contract inteligent numit unbanked. Oricine poate retrage ether dintr-un contract nebancarat atâta timp cât soldurile mesajului msg.sender (adică apelantul withdraw
funcția ) este mai mare sau egală cu suma solicitată pentru retragere.
function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount;
}
Observați că există un cuvânt cheie call folosit pentru a trimite cantitatea necesară de eter către msg.sender
. Un atacator poate exploata acest lucru creând un contract numit Thief în care apelează funcția de retragere în a fallback()
funcţie. A fallback()
funcția din Solidity este o funcție specială care este executată atunci când eterul este trimis la contractul inteligent.
Aceasta înseamnă că un atacator este capabil apelați recursiv funcția de retragere. Astfel, înainte ca contractul inteligent să se actualizeze, soldurile de msg.sender
la ultima linie de cod, atacatorul a retras deja eterul de mai multe ori. Acest lucru ar putea fi evitat dacă soldurile sunt actualizate înainte de a utiliza cuvântul cheie call, urmând astfel a verificări-efecte-interacţiuni model.
Impact:
primul atac de reintrare s-a întâmplat în 2016 pe o DAO (Organizație autonomă descentralizată) care a dus la un hack-uri de aproximativ 50 de milioane de dolari. Pentru a inversa acest hack, comunitatea Ethereum a împărțit blockchain-ul Ethereum, care a dat naștere ETC (Ethereum Classic) și ETH (Ethereum).
Overflow aritmetic și Underflow
Scenariu din lumea reală:
Să jucăm un joc de gândire. Acesta constă dintr-o roată învârtită, iar câștigătorul este hotărât pe baza celui mai mare număr pe care îl poate obține în timp ce rotește roata. Roata este marcată peste tot de la 256 la -256.
Regulile jocului sunt că indicatorul pentru toți jucătorii se sprijină pe 0 la începutul fiecărei rotiri. Iar un jucător are voie să se rotească numai în direcția numerelor negative. Cum vei câștiga acest joc?
O strategie bună pentru a câștiga acest joc de fiecare dată ar fi să învârți roata cu o astfel de putere încât roata să se învârtească până la -256 și apoi să se transforme la 256 dintr-o singură mișcare. Acest lucru este posibil deoarece 256 vine imediat după -256 pe roată. Aceasta este ceea ce numim o depășire aritmetică. Iar overflow aritmetic este doar invers.
Exemplu de cod din lumea reală:
An underflow sau overflow se întâmplă atunci când o operație aritmetică atinge minimul sau maximul ei.
function withdraw(uint _amount) public { require(balances[msg.sender] - _amount > 0); address payable to = payable(msg.sender); to.transfer(_amount); balances[msg.sender] -= _amount;
}
_amount
parametrul funcției de retragere este un întreg fără semn. Valoarea mapării soldurilor (care este ca un dicționar în python sau o pereche cheie-valoare în C++ sau Java) este, de asemenea, un întreg fără semn.
mapping(address => uint256) public balances
Declarația cerută verifică dacă soldurile de msg.sender
este pozitiv sau nu. Dar această afirmație va fi întotdeauna adevărată chiar dacă suma este mai mare decât soldurile msg.sender
. Acest lucru se datorează faptului că atât balances
și _amount
variabilele sunt de tipul întreg fără semn și rezultatul lor aritmetic (după underflow) va fi, de asemenea, un întreg fără semn!
Și după cum vă amintiți, un întreg fără semn este întotdeauna pozitiv. Aceasta înseamnă că un atacator poate retrage o cantitate nelimitată de Ether din contractul inteligent! Puteți găsi un exemplu detaliat și un cod de implementare pentru această vulnerabilitate aici.
Un alt lucru esențial de remarcat aici este că operația aritmetică între două numere întregi fără semn este, de asemenea, un întreg fără semn. Poate fi periculos dacă acest lucru este trecut cu vederea în contractele inteligente, deoarece poate duce la încălcări nedorite de securitate!
function votes(uint postId, uint upvote, uint downvotes) { if (upvote - downvote < 0) { deletePost(postId) }
}
După cum probabil ați observat în exemplul de mai sus, declarația if este destul de inutilă ca upvote - downvote
va fi întotdeauna pozitiv. Și postarea va fi ștearsă chiar dacă downvotes
este mai mare decât upvotes
. Pentru a evita astfel de atacuri, este recomandat să folosiți o versiune a compilatorului Solidity mai mare decât 0.8.0.
Impact:
O monedă numită monedă PoWH a fost lansat în 2017. Deși a fost un joc Ponzi, el însuși a fost piratat din cauza unei erori de depășire aritmetică care a dus la o pierdere de aproximativ 866 ETH sau 950,000 USD la acel moment. Puteți citi despre asta în detaliu aici.
Trebuie citit: Lecții din atacul asupra lui Tinyman, cel mai mare DEX din Algorand
Atacul de refuz al serviciului
Scenariu din lumea reală:
Imaginați-vă că sunteți într-o universitate Bitcoin Tech. Totul pare în regulă, cu excepția faptului că există o masă comună pentru toată lumea. Și, din păcate, sunt puțini oameni din altă clasă care reușesc să ocupe întotdeauna masa de mese înaintea oricui din clasa ta.
În scenariul practic, ei refuză serviciul esențial tuturor, ceea ce duce la pierderea unui timp prețios. Acesta este ceea ce numim un „atac de refuzare a serviciului”.
Exemplu de cod din lumea reală:
În jocul numit Regele-eterului, oricine poate deveni rege. Dar regula pentru a deveni rege este ca o persoană să depună mai mult eter decât regele actual. Acest lucru se poate face apelând la claimThrone()
funcția contractului Regele Eterului în care persoana trimite eter direct regelui anterior și devine noul rege.
function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); (bool sent, ) = king.call{value: balance}(""); require(sent, "Failed to send Ether"); balance = msg.value; king = msg.sender; }
După cum probabil ați ghicit, acest cod este vulnerabil la un atac DoS, dar cum? Pentru aceasta, va trebui să înțelegeți că există două tipuri de adrese în Ethereum - primul este adresa unui extern contul deținut sau pur și simplu adresa unui portofel, iar al doilea este adresa contractului. Acum eterul poate fi trimis de la oricare dintre aceste tipuri de adrese.
Dacă aceasta, eterul este trimis prin adresa contractului, atunci contractul va deveni rege. Dar să presupunem că acest nou contract nu are un fallback()
funcție care este necesară dacă contractul dorește să accepte eter. Apoi, dacă vine o persoană nouă și încearcă să sune claimThrone()
funcția, va eșua întotdeauna!
Observați că acest lucru se întâmplă și parțial din cauza claimThrone()
funcția verifică în mod explicit dacă transferul de eter a avut succes sau nu în a doua declarație necesară. Puteți găsi codul complet și puteți face un atac DoS asupra acestuia aici.
De asemenea, este posibil ca un cod să fie vulnerabil la un atac DoS dacă codul are o buclă peste o serie de dimensiuni mari. Acest lucru se întâmplă deoarece limita de gaz poate fi depășită în astfel de cazuri. Puteți citi despre asta aici.
Impact:
Un joc numit Guvernamental, care se pare că era o schemă Ponzi, a rămas blocat cu 1100 eter, deoarece era nevoie de o cantitate mare de gaz pentru a procesa plata.
Aleatorie nesigură
Scenariu din lumea reală:
A fost odată un bărbat pe nume Hesky, care era întotdeauna însoțit de maimuța lui Pesky. Hesky a condus jocuri de loterie și a făcut profituri bune. Într-o zi, Alice l-a observat pe Hesky holbându-se cu atenție la maimuța lui Pesky. Apoi l-a văzut scriind ceva pe o bucată de hârtie și a sigilat-o într-un plic. Curiosă, a decis să investigheze mai departe.
Mai târziu în acea seară, Alice a văzut că câștigătorul loteriei a fost decis prin deschiderea publică a plicului sigilat. După ce l-a urmărit câteva zile, Alice și-a dat seama că Hesky a decis numărul câștigător la loterie uitându-se la gesturile lui Pesky (de exemplu, dacă maimuța s-a scărpinat în cap, Hesky a notat 10)! Acum Alice avea formula pentru a câștiga fiecare loterie și trebuia doar să cumpere biletul de loterie cu numărul potrivit!
Hesky a presupus că modul său „aleatoriu” de a decide câștigătorul la loterie nu poate fi niciodată descoperit, dar într-adevăr a greșit.
Exemplu de cod din lumea reală:
În acest exemplu, un număr aleatoriu este generat pe baza hash-ului combinației dintre numărul unui bloc și marcajul temporal al blocului. acest hash este apoi atribuit variabilei răspuns. Acum, oricine ghiceste acest număr (aparent) aleatoriu, primește 1 eter. Crezi că acest lucru este de neatins?
function guess(uint _guess) public { uint answer = uint( keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) ); if (_guess == answer) { (bool sent, ) = msg.sender.call{value: 1 ether}(""); require(sent, "Failed to send Ether"); } }
Nu! Un atacator poate încă ghici acest număr aleatoriu prin simpla copiere a lipirii codului pentru a genera valoarea atribuită variabilei de răspuns și transmiterea aceleiași variabile de răspuns către guess()
funcţie!
guessTheRandomNumber.guess(answer);
Puteți găsi codul complet aici. Pentru a evita acest atac, se recomandă utilizarea unei funcții aleatorii verificabile, cum ar fi VRF cu lanțuri.
Impact:
Aproximativ 400 ETH au fost pierdute din cauza unui atac asupra Loteria Smart Billions contracta. În mod surprinzător, chiar și loteria contractului în sine a fost o schemă Ponzi (ai!).
Manipularea timpului
Scenariu din lumea reală:
Lui Satoshi îi place să mănânce prăjituri. Îi plac toate tipurile de fursecuri pe care le face mama lui. Dar mama lui este foarte strictă și consideră că a mânca prea multe prăjituri nu este bine pentru el. Așa că mama lui face o regulă că el va primi prăjiturile abia la ora 8.
Chiar în acea zi, la 7:45, Satoshi aleargă la mama lui și îi cere prăjituri. Mama lui întreabă: „Cât este ceasul?”
„Este ora 8!” – răspunde el.
"Bine. Atunci ia prăjiturile din dulapul meu.”
Și astfel, Satoshi a reușit să manipuleze timpul cu succes cu 15 minute, astfel încât să-și poată obține prăjiturile! Ce tip flămând de prăjituri!
Exemplu de cod din lumea reală:
Marca temporală a unui bloc poate fi manipulată de aproximativ 15 secunde de un miner. În acest fel, un miner poate seta un marcaj temporal favorabil și poate include tranzacția sa în același bloc pe care îl extrage. Functia play()
aparține unui contract de joc numit G-Dot.
function play() public { require(now > 1640392200 && neverPlayed == true); neverPlayed = false; msg.sender.transfer(1500 ether);
}
Acest contract recompensează 1500 eter jucătorului care este primul care apelează funcția de joc. Dar, după cum puteți vedea, funcția de redare poate fi apelată numai dacă acum sau block.timestamp al tranzacției care conține apelul către play()
funcția, este mai mare decât timp de epocă 1640392200.
Un miner poate manipula cu ușurință acest marcaj de timp și poate include tranzacția sa de a apela la play()
funcţionează în acelaşi bloc, astfel încât el însuşi este primul jucător. În acest fel este garantat că minerul va câștiga jocul!
Impact:
Block.timestamp a fost folosit pentru a genera numere aleatorii în Guvernamental și era astfel vulnerabil la atacurile de manipulare a timpului.
Contactați QuillAudits
QuillAudits este o platformă securizată de auditare a contractelor inteligente concepută de QuillHash
Tehnologii.
Este o platformă de audit care analizează și verifică riguros contractele inteligente pentru a verifica vulnerabilitățile de securitate prin revizuire manuală eficientă cu instrumente de analiză statică și dinamică, analizoare de gaze, precum și asimilatoare. Mai mult, procesul de audit include, de asemenea, teste unitare extensive, precum și analize structurale.
Efectuăm atât audituri de contracte inteligente, cât și teste de penetrare pentru a găsi potențialul
vulnerabilități de securitate care ar putea afecta integritatea platformei.
Dacă aveți nevoie de asistență în auditul contractelor inteligente, nu ezitați să contactați experții noștri aici!
Pentru a fi la curent cu munca noastră, alăturați-vă comunității noastre: -
Twitter | LinkedIn | Facebook | Telegramă
Mesaj Ghid pentru începători pentru auditarea inteligentă a contractelor: partea 1 a apărut în primul rând pe Blogul Quillhash.
Sursa: https://blog.quillhash.com/2022/01/19/beginners-guide-to-smart-contract-auditing-part-1/
- "
- &
- 000
- 2016
- 7
- Despre Noi
- Cont
- adresa
- TOATE
- deja
- Cu toate ca
- analiză
- de audit
- autonom
- Început
- CEL MAI BUN
- Bitcoin
- blockchain
- Bug
- cumpăra
- apel
- cazuri
- Verificări
- clasic
- cod
- Monedă
- combinaţie
- Comun
- comunitate
- conține
- contract
- contracte
- fursecuri
- ar putea
- Crearea
- cripto
- Curent
- DAO
- zi
- descentralizată
- Denial of Service
- detaliu
- Dex
- jos
- cu ușurință
- mânca
- ETH
- Eter
- ethereum
- Etanșul blocului
- Ethereum Classic
- exemplu
- Exploata
- capăt
- First
- Gratuit
- funcţie
- joc
- Jocuri
- GAS
- genera
- GitHub
- merge
- bine
- ghida
- hack
- hacks
- hașiș
- cap
- aici
- Cum
- HTTPS
- investiga
- IT
- Java
- alătura
- a sari
- Rege
- limbă
- mare
- Linie
- Lung
- cautati
- loterie
- om
- milion
- mamă
- numere
- organizație
- Hârtie
- Model
- Plătește
- oameni
- bucată
- platformă
- Joaca
- player
- Ponzi
- Schema Ponzi
- putere
- proces
- Programatorii
- Programare
- public
- inversa
- revizuiască
- Recompense
- norme
- Satoshi
- securitate
- set
- inteligent
- contract inteligent
- Contracte inteligente
- So
- soliditate
- ceva
- Rotire
- împărţi
- început
- Declarație
- Strategie
- de succes
- Reușit
- tech
- teste
- Prin
- timp
- Unelte
- tranzacție
- unbanked
- universitate
- actualizări
- valoare
- Vulnerabilitățile
- vulnerabilitate
- vulnerabil
- Portofel
- Ce
- Roată
- OMS
- câştiga
- Apartamente
- scris