Analyseur: Slither
Description: Framework d'analyse statique open-source pour Solidity
githib: https://github.com/trailofbits/slither
Il s'agit d'un analyseur de code statique écrit en python. Il sait comment surveiller les variables, les appels et détecte une telle liste de vulnérabilités . Chaque vulnérabilité a un lien avec une description, et si vous êtes nouveau sur Solidity, il est logique que vous appreniez à connaître tout le monde.
Slither peut fonctionner comme un module python et fournir au programmeur une interface pour l'audit selon son propre plan. Un exemple simple et illustratif de ce que peut faire Slither peut être vu ici .
Nous reviendrons sur les scénarios d'analyse à la fin de l'article, mais pour l'instant exécutez Slither:
git clone https://github.com/trailofbits/slither.git cd slither docker build -t slither .
et essayez d'analyser notre contrat.
Nous allons dans le répertoire avec constructor-eth-booking et essayons d'exécuter slither depuis le docker:
$ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol
nous obtenons l'erreur "Le fichier source nécessite une version différente du compilateur", et maintenant nous devons mettre la version de solc=0.4.20
dans le docker slither. Pour ce faire, nous corrigeons le Dockerfile de Slither lui-même, comme indiqué dans la section 2 de l'introduction , c'est-à-dire quelque part à la fin du Dockerfile, nous ajoutons la ligne:
COPY --from=ethereum/solc:0.4.20 /usr/bin/solc /usr/bin
, reconstruisez l'image, courez, applaudissez, tout se compile.
Nous voyons la sortie, un avertissement sur différents «pragma» et sur les noms de variables incorrects comme le Parameter '_price' of Booking.Booking (flattened.sol#73) is not in mixedCase
. Les analyseurs donnent beaucoup d'avertissements, mais nous recherchons de vrais bugs, et nous ne ferons pas attention à la bagatelle. Nous filtrons tous les messages sur mixedCase, maintenant ce n'est pas à la hauteur du style:
$ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol 2>&1 | fgrep -v 'mixedCase'
Les vrais programmeurs manquent tout ce qui est vert, tout ce qui est rouge, et voici ce que Slither a trouvé dans le contrat en plus des faux positifs:
Booking.refundWithoutCancellationFee (flattened.sol#243-250) sends eth to arbirary user Dangerous calls: - client.transfer(address(this).balance) (flattened.sol#249) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations INFO:Detectors: Booking.refundWithCancellationFee (flattened.sol#252-259) sends eth to arbirary user Dangerous calls: - owner.transfer(m_cancellationFee) (flattened.sol#257) - client.transfer(address(this).balance) (flattened.sol#258) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations
Maintenant, regardez ce qui ne va pas avec cette fonction dans le contrat:
/************************** PRIVATE **********************/ function refundWithoutCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.OFFER); client.transfer(address(this).balance); } function refundWithCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.CANCELED); owner.transfer(m_cancellationFee); client.transfer(address(this).balance); }
dans ce cas, par exemple, la fonction refundWithoutCancellationFee()
est appelée comme ceci:
function rejectPayment() external onlyOwner onlyState(State.PAID) { refundWithoutCancellationFee(); } function refund() external onlyClient onlyState(State.PAID) { refundWithoutCancellationFee(); }
Hmm, formellement, il n'y a pas d'erreur: les appels sont protégés par toutes sortes de onlyOwner
, mais Slither jure que, selon eux, la diffusion est envoyée sans aucun contrôle à l'intérieur de remboursementWithoutCancellationFee (). Et il a raison, la fonction elle-même n'a vraiment presque aucune restriction. Que ce soit privé, et il est appelé à partir des wrappers "rejettePaiement ()" "remboursement ()" avec les restrictions nécessaires, mais dans ce formulaire, si vous modifiez le contrat, il y a un grand risque d'oublier les restrictions et de coller le refundWithoutCancellationFee()
appel à refundWithoutCancellationFee()
vers un autre endroit, à la disposition de l'attaquant. Ainsi, même s'il n'y a formellement aucune vulnérabilité, les informations se sont avérées utiles - il s'agit au moins d'un niveau «d'avertissement», si la mission prévoit de développer davantage le code du contrat. Dans ce cas, deux fonctions de participants différents utilisent le même code, et cette décision a été prise d'économiser du gaz - le contrat est unique et le coût de son calcul est un facteur important.
J'ai revérifié que Slither maudissait pour toute diffusion, et j'ai transféré le corps de la fonction directement aux «rejeterPaiement ()» et «remboursement ()» ci-dessus, l'avertissement a disparu, c'est-à-dire Slither s'est rendu compte que maintenant la diffusion n'est pas envoyée sans vérification d'adresse. Bon début!
Voyons maintenant comment Slither surveille l'initialisation des variables, pour cela nous commentons deux initialisations:
- m_fileHash = _fileHash; + // m_fileHash = _fileHash; - m_price = _price; + // m_price = _price;
le premier n'est pas très important, en termes de trous, sauf pour le gaspillage de ressources, car m_fileHash n'est utilisé nulle part, il est simplement stocké sur la blockchain lors de la création d'un contrat. Mais m_price est utilisé, et Slither jure correctement que m_price n'est initialisé nulle part, bien qu'il soit utilisé:
Booking.m_price (flattened.sol#128) is never initialized. It is used in: - fallback (flattened.sol#144-156)
Eh bien, c'est un truc simple, comme prévu, tout a bien fonctionné.
Nous allons maintenant ajouter la réentrance si chère à tout le monde dans le contrat: nous allons changer l'état du contrat après un appel externe. Nous apportons les modifications suivantes:
function refundWithoutCancellationFee() private { address client = m_client; - m_client = address(0); - changeState(State.OFFER); - client.transfer(address(this).balance); + client.call.value(address(this).balance)(); + m_client = address(0); + changeState(State.OFFER); }
J'ai dû remplacer le transfert par un appel, car la variante de transfert ne jure pas car le transfert envoie un appel avec un minimum de gaz, et un rappel est impossible (bien que lors du passage à la fourche de Constantinople dans Ethereum, le prix du gaz a été modifié et cela a réactivé l'attaque de réentrance en utilisant le transfert .
Résultat de la recherche de réentrance:
Reentrancy in Booking.refundWithoutCancellationFee (flattened.sol#243-253): External calls: - client.call.value(address(this).balance)() (flattened.sol#245) State variables written after the call(s): - m_client (flattened.sol#246)
C'est bien, au moins ça ne donnera pas de variables d'état après des appels externes, et c'est très bien.
Si vous vous déplacez le long de la liste, les vulnérabilités restantes de la liste recherchent simplement des méthodes spécifiques dans le code ou des modèles connus qui, bien sûr, si l'accès au balisage par python est disponible, fonctionne de manière assez fiable. C'est-à-dire schémas bien connus Slither ne manquera pas.
Maintenant, je vais apporter des modifications qui montrent parfaitement les spécificités du travail des analyseurs statiques:
- client.transfer(address(this).balance + for (uint i=0; i < 1; i++) { + client.transfer(address(this).balance - 999999999999999999); + }
et le résultat:
Booking.refundWithoutCancellationFee has external calls inside a loop: - client.transfer(address(this).balance - 999999999999999999) (flattened.sol#252) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description/_edit#calls-inside-a-loop
Le cycle est exécuté une fois et est dégénéré - par conséquent, l'avertissement émis est faux positif, et l'absence d'avertissement sur l'arithmétique dangereuse est faux négatif. Analyse des types, résultats des opérations, comptage des appels - les tâches ne sont pas destinées aux analyseurs statiques. Par conséquent, comprenez clairement quelles erreurs Slither trouvera et lesquelles devraient être recherchées à l'aide d'autres outils.
Nous avons promis de mentionner la possibilité d'écrire nos propres scripts pour les tests et la sortie de toute information intéressante sur le contrat en utilisant la clé --print
. De ce point de vue, Slither est un excellent outil pour CI. Les développeurs d'un grand système de contrat connaissent les noms des variables critiques pour la sécurité: soldes, tailles de commission, indicateurs et peuvent écrire un script de test qui bloquera toutes les modifications du code qui, par exemple, écraseront une variable importante ou changeront les variables d'état après un appel externe, et son analyse hautement prévisible est un excellent outil pour une utilisation dans les crochets.
La tâche de Slither est de vous sauver des bogues stupides, de trouver des schémas dangereux bien connus et d'avertir le développeur. Dans cette version, c'est un bon outil pour un développeur novice sur Solidity, vous demandant immédiatement comment écrire du code dans Solidity correctement.
Résumé
Dans mes tests personnels, je mettrais Slither quatre pour la polyvalence, la simplicité et la facilité d'utilisation, ainsi que pour des scripts de test simples et compréhensibles et l'adaptabilité à CI.
Slither a trouvé en toute confiance un véritable AVERTISSEMENT associé à l'utilisation de la fonction d'envoi de la diffusion, il a trouvé tous les bugs introduits. Il ne pourrait pas faire face uniquement à l'analyse dynamique, ce qui ne devrait pas être fait officiellement, sinon il devrait sacrifier l'universalité, la prévisibilité et la facilité d'utilisation.
Dans le prochain article, nous traiterons de l'analyseur Mythril, mais voici la table des matières des articles prêts ou prévus pour l'écriture:
Partie 1. Introduction. Compilation, aplatissement, versions de Solidity
Partie 2. Slither (cet article)
Partie 3. Mythril
Partie 4. Manticore (en cours d'écriture)
Partie 5. Echidna (en cours d'écriture)