Rejoignez-Nous sur

Ethereum Smart Contract Security Recommendations | par ConsenSys | Juil.2020

0*U5wR5jVXWejlPqoj

News

Ethereum Smart Contract Security Recommendations | par ConsenSys | Juil.2020

10 modèles de sécurité de contrat intelligents à suivre lorsque vous construisez sur Ethereum.

ConsenSys

Comme nous l'avons vu dans le État d'esprit de sécurité des contrats intelligents, un développeur Ethereum vigilant garde toujours à l'esprit cinq principes:

  • Préparez-vous à l'échec
  • Déployez soigneusement
  • Gardez les contrats simples
  • Tiens-toi à jour
  • Soyez conscient des particularités d'EVM

Dans cet article, nous allons plonger dans les particularités d'EVM et parcourir une liste de modèles à suivre lors du développement de tout système de contrat intelligent sur Ethereum. Cette pièce est principalement destinée aux développeurs intermédiaires d'Ethereum. Si vous en êtes encore aux premiers stades de l’exploration, consultez la ConsenSys Academy programme de développeur de blockchain à la demande.

D'accord, plongeons-nous.

Soyez prudent lorsque vous effectuez des appels externes

Les appels à des contrats intelligents non approuvés peuvent introduire plusieurs risques ou erreurs inattendus. Les appels externes peuvent exécuter du code malveillant dans ce contrat ou tout autre contrat dont il dépend. Par conséquent, traitez chaque appel externe comme un risque de sécurité potentiel. Lorsqu'il n'est pas possible ou indésirable de supprimer les appels externes, utilisez les recommandations dans le reste de cette section pour minimiser le danger.

Marquer les contrats non approuvés

Lorsque vous interagissez avec des contrats externes, nommez vos variables, méthodes et interfaces de contrat d'une manière qui indique clairement que l'interaction avec eux est potentiellement dangereuse. Cela s'applique à vos propres fonctions qui appellent des contrats externes.

// bad
Bank.withdraw(100); // Unclear whether trusted or untrusted

function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe
Bank.withdraw(amount);
}

// good
UntrustedBank.withdraw(100); // untrusted external call
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp

function makeUntrustedWithdrawal(uint amount) {
UntrustedBank.withdraw(amount);
}

Évitez les changements d'état après des appels externes

Que ce soit en utilisant appels bruts (de la forme someAddress.call ()) ou appels de contrat (de la forme ExternalContract.someMethod ()), supposons que du code malveillant pourrait s'exécuter. Même si ExternalContract n'est pas malveillant, un code malveillant peut être exécuté par n'importe quel contrat il appels.

Un danger particulier est que le code malveillant peut détourner le flux de contrôle, entraînant des vulnérabilités dues à la réentrance. (Voir Réentrance pour une discussion plus approfondie de ce problème).

Si vous passez un appel à un contrat externe non approuvé, éviter les changements d'état après l'appel. Ce modèle est aussi parfois appelé modèle de vérification-effets-interactions.

Voir SWC-107

N'utilisez pas transfer () ou send ().

.transfer () et .send () transmettent exactement 2 300 gaz au destinataire. Le but de cette allocation de gaz codé en dur était d'empêcher vulnérabilités de réentrance, mais cela n'a de sens que dans l'hypothèse où les coûts du gaz sont constants. EIP 1884, qui faisait partie de la fourche dure d'Istanbul, a augmenté le coût du gaz de l'opération SLOAD. La fonction de secours d’un contrat a donc coûté plus de 2 300 gaz. Nous vous recommandons d'arrêter d'utiliser .transfer () et .send () et d'utiliser plutôt .call ().

// bad
contract Vulnerable {
function withdraw(uint256 amount) external {
// This forwards 2300 gas, which may not be enough if the recipient
// is a contract and gas costs change.
msg.sender.transfer(amount);
}
}

// good
contract Fixed {
function withdraw(uint256 amount) external {
// This forwards all available gas. Be sure to check the return value!
(bool success, ) = msg.sender.call.value(amount)("");
require(success, "Transfer failed.");
}
}

Notez que .call () ne fait rien pour atténuer les attaques de réentrance, donc d'autres précautions doivent être prises. Pour éviter les attaques de réentrance, utilisez le modèle de vérification-effets-interactions.

Gérer les erreurs dans les appels externes

Solidity propose des méthodes d'appel de bas niveau qui fonctionnent sur les adresses brutes: address.call (), address.callcode (), address.delegatecall () et address.send (). Ces méthodes de bas niveau ne lèvent jamais d'exception, mais renverront false si l'appel rencontre une exception. D'autre part, appels de contrat (par exemple, ExternalContract.doSomething ()) propage automatiquement un lancer (par exemple, ExternalContract.doSomething () lance également si doSomething () lance).

Si vous choisissez d'utiliser les méthodes d'appel de bas niveau, assurez-vous de gérer la possibilité que l'appel échoue, en vérifiant la valeur de retour.

// bad
someAddress.send(55);
someAddress.call.value(55)(""); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result
someAddress.call.value(100)(bytes4(sha3("deposit()"))); // if deposit throws an exception, the raw call() will only return false and transaction will NOT be reverted

// good
(bool success, ) = someAddress.call.value(55)("");
if(!success) {
// handle failure code
}

ExternalContract(someAddress).deposit.value(100)();

Voir SWC-104

Favoriser tirer plus de pousser pour les appels externes

Les appels externes peuvent échouer accidentellement ou délibérément. Pour minimiser les dommages causés par de telles défaillances, il est souvent préférable d'isoler chaque appel externe dans sa propre transaction qui peut être initiée par le destinataire de l'appel. Cela est particulièrement pertinent pour les paiements, où il est préférable de laisser les utilisateurs retirer des fonds plutôt que de leur envoyer automatiquement des fonds. (Cela réduit également le risque de problèmes avec la limite de gaz.) Évitez de combiner plusieurs transferts d'éther en une seule transaction.

// bad
contract auction {
address highestBidder;
uint highestBid;

function bid() payable {
require(msg.value >= highestBid);

if (highestBidder != address(0)) {
(bool success, ) = highestBidder.call.value(highestBid)("");
require(success); // if this call consistently fails, no one else can bid
}

highestBidder = msg.sender;
highestBid = msg.value;
}
}

// good
contract auction {
address highestBidder;
uint highestBid;
mapping(address => uint) refunds;

function bid() payable external {
require(msg.value >= highestBid);

if (highestBidder != address(0)) {
refunds(highestBidder) += highestBid; // record the refund that this user can claim
}

highestBidder = msg.sender;
highestBid = msg.value;
}

function withdrawRefund() external {
uint refund = refunds(msg.sender);
refunds(msg.sender) = 0;
(bool success, ) = msg.sender.call.value(refund)("");
require(success);
}
}

Voir SWC-128

Ne déléguez pas l'appel à du code non fiable

La fonction delegatecall appelle les fonctions d'autres contrats comme si elles appartenaient au contrat de l'appelant. Ainsi, l'appelé peut changer l'état de l'adresse appelante. Cela peut être précaire. Un exemple ci-dessous montre comment l'utilisation de delegatecall peut entraîner la destruction du contrat et la perte de son solde.

contract Destructor
{
function doWork() external
{
selfdestruct(0);
}
}

contract Worker
{
function doWork(address _internalWorker) public
{
// unsafe
_internalWorker.delegatecall(bytes4(keccak256("doWork()")));
}
}

Si Worker.doWork () est appelé avec l'adresse du contrat Destructor déployé comme argument, le contrat Worker s'autodétruit. Déléguer l'exécution uniquement à des contrats approuvés, et jamais à une adresse fournie par l'utilisateur.

avertissement

Ne présumez pas que les contrats sont créés avec un solde nul. Un attaquant peut envoyer de l'éther à l'adresse d'un contrat avant sa création. Les contrats ne doivent pas supposer que son état initial contient un solde nul. Voir numéro 61 pour plus de détails.

Voir SWC-112

Méfiez-vous de coder un invariant qui vérifie strictement l'équilibre d'un contrat.

Un attaquant peut envoyer de l'éther de force vers n'importe quel compte. Cela ne peut pas être évité (même avec une fonction de repli qui effectue un retour ()).

L'attaquant peut le faire en créant un contrat, en le finançant avec 1 wei et en invoquant l'autodestruction (victimAddress). Aucun code n'est invoqué dans victimAddress, il ne peut donc pas être empêché. Cela est également vrai pour la récompense de bloc qui est envoyée à l'adresse du mineur, qui peut être n'importe quelle adresse arbitraire.

De plus, comme les adresses de contrat peuvent être précalculées, l'éther peut être envoyé à une adresse avant le déploiement du contrat.

Voir SWC-132

De nombreuses applications nécessitent que les données soumises soient privées jusqu'à un certain moment pour fonctionner. Jeux (par exemple, ciseaux à papier de roche en chaîne) et mécanismes d'enchères (par exemple, offre scellée Enchères Vickrey) sont deux grandes catégories d'exemples. Si vous créez une application où la confidentialité est un problème, assurez-vous d'éviter d'obliger les utilisateurs à publier des informations trop tôt. La meilleure stratégie consiste à utiliser régimes d'engagement avec des phases distinctes: valider d'abord en utilisant le hachage des valeurs et dans une phase ultérieure révéler les valeurs.

Exemples:

  • Dans les ciseaux à papier de roche, demandez aux deux joueurs de soumettre un hachage de leur mouvement prévu en premier, puis demandez aux deux joueurs de soumettre leur mouvement; si le mouvement soumis ne correspond pas au hachage, jetez-le.
  • Dans une enchère, demandez aux joueurs de soumettre un hachage de leur valeur d'enchère dans une phase initiale (avec un dépôt supérieur à leur valeur d'enchère), puis de soumettre leur valeur d'enchère dans la deuxième phase.
  • Lors du développement d'une application qui dépend d'un générateur de nombres aléatoires, l'ordre doit toujours être (1) les joueurs soumettent des coups, (2) nombre aléatoire généré, (3) les joueurs ont payé. Beaucoup de gens recherchent activement des générateurs de nombres aléatoires; les meilleures solutions actuelles incluent des en-têtes de bloc Bitcoin (vérifiés par http://btcrelay.org), les schémas de validation de validation de hachage (c'est-à-dire qu'une partie génère un nombre, publie son hachage pour «valider» la valeur, puis révèle la valeur plus tard) et RANDAO. Ethereum étant un protocole déterministe, vous ne pouvez utiliser aucune variable du protocole comme un nombre aléatoire imprévisible. Sachez également que les mineurs contrôlent dans une certaine mesure la valeur block.blockhash ()*.

Ne soumettez pas les processus de remboursement ou de réclamation à une partie spécifique effectuant une action particulière sans autre moyen de retirer les fonds. Par exemple, dans un jeu de ciseaux à papier, une erreur courante est de ne pas effectuer de paiement tant que les deux joueurs n'ont pas soumis leurs mouvements; cependant, un joueur malveillant peut "chagriner" l'autre en ne soumettant simplement pas son coup – en fait, si un joueur voit le coup révélé de l'autre joueur et détermine qu'il a perdu, il n'a aucune raison de soumettre son propre coup. Ce problème peut également se poser dans le contexte du règlement de la chaîne d'État. Lorsque de telles situations sont un problème, (1) fournir un moyen de contourner les participants non participants, peut-être par une limite de temps, et (2) envisager d'ajouter une incitation économique supplémentaire pour les participants à soumettre des informations dans toutes les situations dans lesquelles ils sont censé le faire.

Méfiez-vous de la négation de l'entier signé le plus négatif

Solidity propose plusieurs types pour travailler avec des entiers signés. Comme dans la plupart des langages de programmation, dans Solidity, un entier signé avec N bits peut représenter des valeurs de -2 ^ (N-1) à 2 ^ (N-1) -1. Cela signifie qu'il n'y a pas d'équivalent positif pour MIN_INT. La négation est implémentée comme la recherche du complément à deux d'un nombre, donc la négation du nombre le plus négatif donnera le même nombre. Cela est vrai pour tous les types d'entiers signés dans Solidity (int8, int16,…, int256).

contract Negation {
function negate8(int8 _i) public pure returns(int8) {
return -_i;
}

function negate16(int16 _i) public pure returns(int16) {
return -_i;
}

int8 public a = negate8(-128); // -128
int16 public b = negate16(-128); // 128
int16 public c = negate16(-32768); // -32768
}

Une façon de gérer cela est de vérifier la valeur d'une variable avant la négation et de la lancer si elle est égale à MIN_INT. Une autre option consiste à s'assurer que le nombre le plus négatif ne sera jamais atteint en utilisant un type avec une capacité plus élevée (par exemple int32 au lieu de int16).

Un problème similaire avec les types int se produit lorsque MIN_INT est multiplié ou divisé par -1.

Nous espérons que ces recommandations ont été utiles. Si vous et votre équipe vous préparez au lancement ou même au début du cycle de vie du développement et que vous avez besoin de la vérification de l'intégrité de vos contrats intelligents, n'hésitez pas à contacter notre équipe d'ingénieurs de sécurité au ConsenSys Diligence. Nous sommes là pour vous aider à lancer et à maintenir vos applications Ethereum en toute confiance.



Traduction de l’article de ConsenSys : Article Original

BlockBlog

Le Meilleur de l'Actualité Blockchain Francophone & Internationale | News, Guides, Avis & Tutoriels pour s'informer et démarrer facilement avec Bitcoin, les Crypto-Monnaies et le Blockchain. En Savoir Plus sur L'Équipe BlockBlog

Commenter cet Article

Commenter cet Article

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Plus dans News

Les Plus Populaires

Acheter des Bitcoin

Acheter des Alt-Coins

Sécuriser vos Cryptos

Vêtements et Produits Dérivés

Top