Vulnérabilités dans Etherium Smart Contracts. Exemples de code

Avec ce billet, je commence une série d'articles sur la sécurité des contrats intelligents Ethereum. Je pense que ce sujet est très pertinent, car le nombre de développeurs augmente comme une avalanche, et il n'y a personne à sauver du "rake". Au revoir - traductions ...

1. Analyse des contrats Live Ethereum pour une erreur d'envoi non contrôlé


Original - Numérisation des contrats Ethereum en direct pour le "Envoi non contrôlé ..."


Auteurs: Zikai Alex Wen et Andrew Miller

La programmation de contrat intelligent Ethereum est connue pour être sujette aux erreurs [1] . Récemment, nous avons vu que plusieurs
les contrats intelligents haut de gamme tels que King of the Ether et The DAO-1.0 contenaient des vulnérabilités causées par des erreurs de programmation.

Depuis mars 2015, les programmeurs de contrats intelligents ont été avertis des dangers de programmation spécifiques qui peuvent survenir lorsque les contrats s'envoient des messages [6] .

Plusieurs guides de programmation recommandent comment éviter les erreurs courantes (dans les livres blancs Ethereum [3] et dans un guide indépendant de UMD [2] ). Bien que ces dangers soient suffisamment compréhensibles pour les éviter, les conséquences d'une telle erreur sont terribles: l'argent peut être bloqué, perdu ou volé.

Les erreurs résultant de ces dangers sont-elles courantes? Existe-t-il des contrats de blockchain Ethereum plus vulnérables mais vivants? Dans cet article, nous répondons à cette question en analysant les contrats sur la blockchain live Ethereum à l'aide du nouvel outil d'analyse que nous avons développé.


Quelle est l'erreur d'envoi non contrôlé?


Pour envoyer un contrat de temps d'antenne à une autre adresse, le moyen le plus simple consiste à utiliser le mot clé send . Cela agit comme une méthode définie pour chaque objet. Par exemple, l'extrait de code suivant peut être trouvé dans un contrat intelligent qui implémente un jeu de société.


/*** Listing 1 ***/ if (gameHasEnded && !( prizePaidOut ) ) { winner.send(1000); //    prizePaidOut = True; } 

Le problème ici est que la méthode d' envoi peut échouer. Si cela ne fonctionne pas, le gagnant ne recevra pas l'argent, mais la variable prizePaidOut sera définie sur True.

Il existe deux cas différents où la fonction winner.send () peut échouer. Nous analyserons la différence entre eux plus tard. Le premier cas est que l'adresse du gagnant est un contrat (pas un compte utilisateur), et le code de ce contrat lève une exception (par exemple, s'il utilise trop de «gaz»). Dans l’affirmative, il s’agit peut-être dans ce cas d’une «erreur du gagnant». Le deuxième cas est moins évident. La machine virtuelle Ethereum dispose d'une ressource limitée appelée « callstack » (profondeur de pile d'appels), et cette ressource peut être utilisée par un autre code de contrat qui a été précédemment exécuté dans une transaction. Si la pile d'appels a déjà été utilisée au moment où la commande d' envoi est exécutée, la commande échouera, quelle que soit la façon dont le gagnant est déterminé. Le prix du gagnant sera détruit sans faute de sa part!



Comment éviter cette erreur?

La documentation Ethereum contient un bref avertissement concernant ce danger potentiel [3] : "Il existe un certain danger lors de l'utilisation de l' envoi - la transmission échoue si la profondeur de la pile d'appels est de 1024 (cela peut toujours être causé par l'appelant), et échoue également si le destinataire "gas" se termine. Par conséquent, pour assurer une diffusion en toute sécurité, vérifiez toujours la valeur de retour de l' envoi, ou mieux encore: utilisez un modèle dans lequel le destinataire retire de l'argent. "

Deux phrases. La première consiste à vérifier la valeur de retour d' envoi pour voir si elle s'est terminée avec succès. Si ce n'est pas le cas, lancez une exception pour restaurer l'état.


  /*** Listing 2 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000)) prizePaidOut = True; else throw; } 

C'est une solution adéquate pour l'exemple actuel, mais pas toujours la bonne décision. Supposons que nous modifions notre exemple de sorte que lorsque le jeu est terminé, le gagnant et le perdant annulent leur fortune. Une application évidente d'une solution «formelle» serait la suivante:


 /*** Listing 3 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (winner.send(1000) && loser.send(10)) prizePaidOut = True; else throw; } 

Cependant, c'est une erreur car elle introduit une vulnérabilité supplémentaire. Bien que ce code protège le gagnant d'une attaque par pile d'appels , il rend également le gagnant et le perdant vulnérables l'un à l'autre. Dans ce cas, nous voulons empêcher une attaque par pile d'appels , mais continuer l'exécution si la commande send échoue pour une raison quelconque.

Par conséquent, même la meilleure meilleure pratique (recommandée dans notre Guide du programmeur Ethereum et Serpent, bien qu'elle s'applique également à Solidity), consiste à rechercher une ressource de pile d'appels . Nous pouvons définir une macro callStackIsEmpty () , qui retournera une erreur si et seulement si callstack est vide.


 /*** Listing 4 ***/ if (gameHasEnded && !( prizePaidOut ) ) { if (callStackIsEmpty()) throw; winner.send(1000) loser.send(10) prizePaidOut = True; } 

Encore mieux, la recommandation de la documentation Ethereum - «Utilisez un modèle dans lequel le destinataire prend l'argent» est un peu cryptique, mais a une explication. La suggestion est de réorganiser votre code afin que l'effet de l'échec d' envoi soit isolé et n'affecte qu'un seul destinataire à la fois. Voici un exemple de cette approche. Cependant, cette astuce est également un anti-modèle. Il accepte la responsabilité de vérifier la pile d'appels aux destinataires eux-mêmes, ce qui permet de tomber dans le même piège.


 /*** Listing 5 ***/ if (gameHasEnded && !( prizePaidOut ) ) { accounts[winner] += 1000 accounts[loser] += 10 prizePaidOut = True; } ... function withdraw(amount) { if (accounts[msg.sender] >= amount) { msg.sender.send(amount); accounts[msg.sender] -= amount; } } 

De nombreux contrats intelligents hautement développés sont vulnérables. La loterie du Roi de l'Air du Trône est le cas le plus connu de cette erreur [4] . Cette erreur n'a été constatée que lorsque le montant de 200 éthers (d'une valeur supérieure à 2000 dollars américains au prix actuel) n'a pas pu obtenir le gagnant légitime de la loterie. Le code correspondant dans King of the Ether est similaire au code de l'extrait 2. Heureusement, dans ce cas, le développeur du contrat a pu utiliser la fonction non liée dans le contrat comme une «dérogation manuelle» pour libérer les fonds bloqués. Un administrateur moins scrupuleux pourrait utiliser la même fonction pour voler la diffusion!


Suite de l' analyse des contrats Live Ethereum pour une erreur d'envoi non contrôlé. 2e partie

Source: https://habr.com/ru/post/fr430602/


All Articles