Benvenuto nella guida per principianti all'auditing dei contratti intelligenti! Uno dei modi migliori per iniziare con l'audit dei contratti intelligenti è entrare e guardare alcuni tipi comuni di vulnerabilità nei contratti intelligenti.
Sarebbe utile se hai già una conoscenza di base del linguaggio di programmazione Solidity di Ethereum. Come vedremo alcuni dei codici scritti dai programmatori di solidità di Noob.
Attacco di rientro
Scenario del mondo reale:
Immagina di avere 50 cioccolatini. Hai una sorellina birichina a cui hai permesso di prendere solo 2 cioccolatini da te alla volta. Inoltre, non dovresti darle più di 10 cioccolatini in un giorno, temendo che le venga la carie. Per garantire questo, ogni sera contate quanti cioccolatini rimangono con voi. Pensi che questo funzionerebbe? O verresti hackerato dalla tua sorellina?
Purtroppo non funzionerà! Tua sorella scopre che non sei a conoscenza di quanti cioccolatini hai fino a sera. Quindi il giorno dopo, la tua sorellina ti viene a trovare 6 volte prima di sera e prende 2 cioccolatini ogni volta! Questo è ciò che chiamiamo attacco di rientro.
Qui stai aggiornando il conteggio dei cioccolatini che hai la sera invece di aggiornare il conteggio ogni volta che tua sorella prende 2 cioccolatini da te. Questo è ciò che accade anche con gli smart contract. Lo smart contract presuppone un particolare equilibrio mentre l'attaccante è in realtà impegnato a ritirare più volte una certa quantità di criptovalute dal contratto.
Esempio di codice del mondo reale:
Questo codice appartiene a uno smart contract chiamato unbanked. Chiunque può prelevare ether da un contratto Unbanked purché i saldi del msg.sender (cioè il chiamante di withdraw
funzione ) è maggiore o uguale all'importo richiesto per il prelievo.
function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount;
}
Si noti che esiste una parola chiave call utilizzata per inviare la quantità richiesta di ether al msg.sender
. Un attaccante può sfruttarlo creando un contratto chiamato Thief in cui chiama la funzione di ritiro in a fallback()
funzione. UN fallback()
La funzione in Solidity è una funzione speciale che viene eseguita quando l'etere viene inviato allo smart contract.
Ciò significa che un attaccante è in grado di farlo chiamare ricorsivamente la funzione di ritiro. Così, prima degli aggiornamenti degli smart contract, i saldi di msg.sender
nell'ultima riga di codice, l'attaccante ha già ritirato l'etere più volte. Ciò potrebbe essere evitato se i saldi vengono aggiornati prima di utilizzare la parola chiave call, quindi a seguito di a controlli-effetti-interazioni pattern.
Impatto:
I primo attacco di rientro in assoluto è successo nel 2016 su una DAO (Organizzazione Autonoma Decentralizzata) che ha portato a circa $ 50 milioni di hack. Per invertire questo hack, la comunità di Ethereum ha diviso la blockchain di Ethereum che ha dato origine a ETC (Ethereum Classic) ed ETH (Ethereum).
Overflow e Underflow aritmetici
Scenario del mondo reale:
Facciamo un gioco di pensiero. Consiste in un Gira la ruota e il vincitore viene deciso in base al numero più alto che riesce a ottenere girando la ruota. La ruota è segnata dappertutto da 256 a -256.
Le regole del gioco sono che il puntatore per tutti i giocatori si trova sullo 0 all'inizio di ogni giro. E un giocatore può girare solo nella direzione di numeri negativi. Come vincerai questa partita?
Una buona strategia per vincere questa partita ogni volta sarebbe quella di girare la ruota con una tale potenza che la ruota giri fino a -256 e poi giri a 256 in una volta sola. Questo è possibile perché 256 arriva subito dopo -256 sulla ruota. Questo è ciò che chiamiamo underflow aritmetico. E l'overflow aritmetico è proprio il contrario di questo.
Esempio di codice del mondo reale:
An underflow o overflow accade quando un'operazione aritmetica raggiunge il suo minimo o massimo.
function withdraw(uint _amount) public { require(balances[msg.sender] - _amount > 0); address payable to = payable(msg.sender); to.transfer(_amount); balances[msg.sender] -= _amount;
}
I _amount
parametro della funzione di ritiro è un numero intero senza segno. Anche il valore della mappatura dei saldi (che è come un dizionario in Python o una coppia chiave-valore in C++ o Java) è un intero senza segno.
mapping(address => uint256) public balances
La dichiarazione richiesta controlla se i saldi di msg.sender
è positivo o meno. Ma questa affermazione sarà sempre vera anche se l'importo è maggiore dei saldi di msg.sender
. Questo perché sia il balances
ed _amount
le variabili sono di tipo intero senza segno e anche il loro risultato aritmetico (dopo underflow) sarà un intero senza segno!
E come ricorderete, un intero senza segno è sempre positivo. Ciò significa che un utente malintenzionato è in grado di prelevare una quantità illimitata di Ether dallo smart contract! È possibile trovare un esempio dettagliato e un codice di implementazione per questa vulnerabilità qui.
Un'altra cosa cruciale da notare qui è che l'operazione aritmetica tra, diciamo, due interi senza segno è anche un intero senza segno. Può essere pericoloso se questo viene trascurato nei contratti intelligenti, poiché può causare violazioni della sicurezza indesiderate!
function votes(uint postId, uint upvote, uint downvotes) { if (upvote - downvote < 0) { deletePost(postId) }
}
Come avrai notato nell'esempio sopra, l'istruzione if è abbastanza inutile come upvote - downvote
sarà sempre positivo. E il post verrà eliminato anche se downvotes
è maggiore upvotes
. Per evitare tali attacchi, si consiglia di utilizzare una versione del compilatore Solidity maggiore di 0.8.0.
Impatto:
Una moneta chiamata Moneta PoWH è stato lanciato nel 2017. Sebbene fosse un gioco Ponzi, esso stesso è stato violato a causa di un bug di overflow aritmetico che ha comportato una perdita di circa 866 ETH o $ 950,000 in quel momento. Puoi leggere questo in dettaglio qui.
Devi leggere: Lezioni dall'attacco a Tinyman, il più grande DEX su Algorand
Denial of Service Attack
Scenario del mondo reale:
Immagina di essere in una Bitcoin Tech University. Tutto sembra a posto tranne che c'è un tavolo da pranzo comune per tutti. E sfortunatamente, ci sono poche persone di un'altra classe che riescono sempre a occupare il tavolo da pranzo prima di chiunque altro della tua classe.
Nello scenario pratico, stanno negando il servizio essenziale a tutti con conseguente perdita di tempo prezioso. Questo è ciò che chiamiamo un "attacco Denial of Service".
Esempio di codice del mondo reale:
Nel gioco chiamato Re dell'etere, chiunque può diventare un re. Ma la regola per diventare re è che una persona dovrebbe depositare più etere dell'attuale re. Questo può essere fatto chiamando il claimThrone()
funzione del contratto King of Ether in cui la persona invia l'etere direttamente al re precedente e diventa il nuovo re.
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; }
Come avrai intuito, questo codice è vulnerabile a un attacco DoS, ma come? Per questo, dovrai capire che ci sono due tipi di indirizzi in Ethereum: il primo è il indirizzo di un esterno account di proprietà o semplicemente l'indirizzo di un portafoglio, e il secondo è il indirizzo del contratto. Ora l'etere può essere inviato da uno di questi tipi di indirizzi.
Se questo, l'etere viene inviato dall'indirizzo del contratto, il contratto diventerà il re. Ma supponiamo che questo nuovo contratto non abbia a fallback()
funzione necessaria se il contratto vuole accettare ether. Quindi se arriva una nuova persona e cerca di chiamare il claimThrone()
funzione, fallirà sempre!
Si noti che ciò accade anche in parte perché il claimThrone()
la funzione controlla esplicitamente se il trasferimento di etere è andato a buon fine o meno nella seconda istruzione richiesta. Puoi trovare il codice completo ed eseguire un attacco DoS su di esso qui.
È anche possibile che un codice sia vulnerabile a un attacco DoS se il codice ha un loop su un array di grandi dimensioni. Questo accade perché il limite di gas può essere superato in questi casi. Puoi leggere a riguardo qui.
Impatto:
Un gioco chiamato Governativo, che apparentemente era uno schema Ponzi, è rimasto bloccato con 1100 ether perché era necessaria una grande quantità di gas per elaborare il pagamento.
Casualità insicura
Scenario del mondo reale:
C'era una volta un uomo di nome Hesky che era sempre accompagnato dalla sua scimmia Pesky. Hesky ha condotto giochi della lotteria e ha realizzato buoni profitti. Un giorno Alice notò che Hesky fissava intensamente la sua scimmia Pesky. Poi lo vide scrivere qualcosa su un pezzo di carta e lo sigillava in una busta. Curiosa, ha deciso di indagare ulteriormente.
Più tardi quella sera, Alice vide che il vincitore della lotteria era stato deciso aprendo pubblicamente la busta sigillata. Dopo averlo osservato per alcuni giorni, Alice ha scoperto che Hesky ha deciso il numero vincente della lotteria guardando i gesti di Pesky (ad esempio se la scimmia si grattava la testa, Hesky ne scriveva 10)! Ora Alice aveva la formula per vincere ogni lotteria e doveva solo acquistare il biglietto della lotteria con il numero giusto!
Hesky aveva ipotizzato che il suo modo "casuale" di decidere il vincitore della lotteria non potesse mai essere capito, ma in effetti non era corretto.
Esempio di codice del mondo reale:
In questo esempio, viene generato un numero casuale in base all'hash della combinazione del numero di un blocco e del relativo timestamp del blocco. questo hash viene quindi assegnato alla variabile di risposta. Ora chiunque indovini questo numero (apparentemente) casuale, viene premiato con 1 Ether. Pensi che questo sia inattaccabile?
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"); } }
No! Un utente malintenzionato può ancora indovinare questo numero casuale semplicemente copiando incollando il codice per generare il valore assegnato alla variabile di risposta e passando la stessa variabile di risposta al guess()
funzione!
guessTheRandomNumber.guess(answer);
Puoi trovare il codice completo qui. Per evitare questo attacco, si consiglia di utilizzare una funzione casuale verificabile come la VRF a catena.
Impatto:
Circa 400 ETH sono andati persi a causa di un attacco al Lotteria Smart Billions contrarre. Sorprendentemente, anche la lotteria dei contratti stessa era uno schema Ponzi (ahi!).
manipolazione del tempo
Scenario del mondo reale:
Satoshi ama mangiare i biscotti. Ama tutti i tipi di biscotti che fa sua madre. Ma sua madre è molto severa e sente che mangiare troppi biscotti non gli fa bene. Quindi sua madre fa una regola che riceverà i biscotti solo alle 8:XNUMX.
Quello stesso giorno alle 7:45, Satoshi corre da sua madre e chiede dei biscotti. Sua madre chiede: "Che ore sono?"
"Sono le 8 in punto!" - lui rispose.
"Bene. Allora prendi i biscotti dalla mia credenza.
E così, Satoshi è stato in grado di manipolare il tempo con successo di 15 minuti in modo da poter ottenere i suoi biscotti! Che tipo affamato di biscotti!
Esempio di codice del mondo reale:
Il timestamp di un blocco può essere manipolato da about 15 secondi da un minatore. In questo modo, un minatore può impostare un timestamp favorevole e includere la sua transazione nello stesso blocco che estrae. La funzione play()
appartiene a un contratto di gioco chiamato G-Dot.
function play() public { require(now > 1640392200 && neverPlayed == true); neverPlayed = false; msg.sender.transfer(1500 ether);
}
Questo contratto premia 1500 ether al giocatore che è il primo a chiamare la funzione di gioco. Ma come puoi vedere, la funzione play può essere chiamata solo se il now o block.timestamp della transazione che contiene la chiamata al play()
funzione, è maggiore di tempo dell'epoca 1640392200
Un minatore può facilmente manipolare questo timestamp e includere la sua transazione di chiamata play()
funzione nello stesso blocco in modo tale che lui stesso sia il primo giocatore. In questo modo è garantito che il minatore vincerà la partita!
Impatto:
Il block.timestamp è stato utilizzato per generare numeri casuali nel file Governo ed era quindi vulnerabile agli attacchi di manipolazione del tempo.
Contatta QuillAudits
QuillAudits è una piattaforma di audit dei contratti intelligenti sicura progettata da QuillHash
Technologies.
È una piattaforma di auditing che analizza e verifica rigorosamente i contratti intelligenti per verificare le vulnerabilità della sicurezza attraverso un'efficace revisione manuale con strumenti di analisi statica e dinamica, analizzatori di gas e assimilatori. Inoltre, il processo di audit include anche test di unità approfonditi e analisi strutturali.
Conduciamo sia audit sui contratti intelligenti che test di penetrazione per trovare il potenziale
vulnerabilità di sicurezza che potrebbero danneggiare l'integrità della piattaforma.
Se hai bisogno di assistenza nell'audit dei contratti intelligenti, non esitare a contattare i nostri esperti qui!
Per essere aggiornato con il nostro lavoro, unisciti alla nostra comunità:-
Twitter | LinkedIn | Facebook | Telegram
Il post Guida per principianti all'audit dei contratti intelligenti: parte 1 apparve prima Blog di Quillhash.
Fonte: https://blog.quillhash.com/2022/01/19/beginners-guide-to-smart-contract-auditing-part-1/
- "
- &
- 000
- 2016
- 7
- Chi siamo
- Il mio account
- indirizzo
- Tutti
- già
- Sebbene il
- .
- revisione
- autonomo
- Inizio
- MIGLIORE
- Bitcoin
- blockchain
- Insetto
- Acquistare
- chiamata
- casi
- Controlli
- classico
- codice
- Moneta
- combinazione
- Uncommon
- comunità
- contiene
- contratto
- contratti
- Cookies
- potuto
- Creazione
- crypto
- Corrente
- DAO
- giorno
- decentrata
- Denial of Service
- dettaglio
- Dex
- giù
- facilmente
- mangiare
- ETH
- etere
- Ethereum
- Ethereum blockchain
- Ethereum Classic
- esempio
- Sfruttare
- sottile
- Nome
- Gratis
- function
- gioco
- Giochi
- GAS
- generare
- GitHub
- andando
- buono
- guida
- incidere
- hack
- hash
- capo
- qui
- Come
- HTTPS
- indagare
- IT
- Java
- join
- saltare
- King
- Lingua
- grandi
- linea
- Lunghi
- cerca
- lotteria
- uomo
- milione
- madre
- numeri
- organizzazione
- Carta
- Cartamodello
- Paga le
- Persone
- pezzo
- piattaforma
- Giocare
- giocatore
- ponzi
- Schema Ponzi
- energia
- processi
- I programmatori
- Programmazione
- la percezione
- invertire
- recensioni
- Rewards
- norme
- Satoshi
- problemi di
- set
- smart
- smart contract
- Smart Contract
- So
- solidità
- qualcosa
- Spin
- dividere
- iniziato
- dichiarazione
- Strategia
- di successo
- Con successo
- Tech
- test
- Attraverso
- tempo
- strumenti
- delle transazioni
- unbanked
- Università
- Aggiornamenti
- APPREZZIAMO
- vulnerabilità
- vulnerabilità
- Vulnerabile
- Portafoglio
- Che
- Ruota
- OMS
- vincere
- Lavora
- scrittura