Comment prévenir les attaques Broken Access Control en

Auteur de l’article

Les attaques par contrôle d’accès défectueux surviennent lorsque un contrat Solidity permet à un appelant non fiable d’accéder à une capacité modifiant l’état qui devrait être restreinte, comme le déplacement d’actifs, la modification d’une implémentation, l’attribution de rôles ou la configuration d’une adresse de confiance. L’attaquant n’a pas besoin de casser la cryptographie : il exploite une vérification d’autorisation manquante, incorrecte ou trop large pour obtenir une élévation effective des privilèges.

L’impact peut s’étendre à l’échelle du protocole, car les fonctions privilégiées contrôlent souvent des fonds collectifs ou des configurations critiques pour la gouvernance. Poly Network a perdu 611,0 millions de dollars le 10-08-2021 sur Ethereum, BSC et Polygon dans un incident classé par DefiLlama comme « Contrôle d’accès inapproprié ». L’autorisation ne se limite donc pas à une question d’interface utilisateur ou à un détail administratif : elle fait partie intégrante de la frontière de sécurité centrale du contrat.

Qu’est-ce qu’une vulnérabilité de contrôle d’accès défectueux dans Solidity ?

Une vulnérabilité de contrôle d’accès défectueux existe lorsque un contrat ne vérifie pas de manière fiable si msg.sender est autorisé à effectuer une action privilégiée, ou lorsque son modèle d’autorisation confère plus d’autorité que nécessaire pour cette action. En Solidity, l’échec apparaît généralement dans les fonctions appelables depuis l’extérieur qui modifient un stockage sensible, transfèrent des actifs, mettent à jour du code ou changent des contreparties de confiance.

Un contrat peut être vulnérable même si une variable owner y est présente. La question cruciale est de savoir si chaque chemin d’exécution privilégié vérifie correctement cette autorisation, y compris dans les fonctions auxiliaires, les initialisateurs, les fonctions de gestion des rôles et les hooks de mise à niveau.

Une attaque typique de contrôle d’accès défectueux suit ce schéma :

  1. L’attaquant identifie une fonction appelable depuis l’extérieur avec des conséquences privilégiées.
  2. La fonction ne vérifie pas l’appelant, vérifie la mauvaise adresse ou se fie à un état contrôlé par l’attaquant.
  3. L’attaquant appelle directement la fonction, contournant toute restriction frontale.
  4. L’appel confère un rôle, modifie une adresse de confiance ou déclenche une opération de déplacement d’actifs.
  5. L’attaquant exploite cette nouvelle capacité pour extraire des fonds ou modifier le comportement du protocole.
  6. Le contrat accepte la transaction, car la machine virtuelle Ethereum (EVM) n’effectue que des vérifications on-chain.

Les boutons d’interface, la documentation ou les procédures opérationnelles prévues ne restreignent pas les appels directs au contrat. N’importe quel compte peut soumettre un calldata à une fonction publique ou externe. Solidity doit donc faire respecter la politique au moment précis où une transition d’état sensible se produit.

Quels modèles Solidity favorisent les attaques de contrôle d’accès et l’élévation de privilèges ?

Les vulnérabilités liées à un contrôle d’accès défectueux proviennent souvent lorsque des développeurs exposent des fonctions de configuration, des aides administratives ou des chemins d’initialisation sans considérer que chaque fonction publique constitue une API potentielle pour l’attaquant. Le modèle dangereux ne se limite pas à un onlyOwner manquant : il inclut aussi une authorization mutable par des appelants arbitraires, hérité incorrectement, appliqué de façon inconsistante ou trop large.

Les schémas d’échec courants incluent :

  • Des setters publics permettant à quiconque de remplacer un treasury, un oracle, une implémentation, un opérateur ou un bénéficiaire.
  • Une fonction de gestion des rôles sans contrôle d’un administrateur.
  • Des initialisateurs pouvant être appelés après le déploiement ou par un compte non prévu.
  • Des fonctions qui vérifient tx.origin au lieu de msg.sender.
  • Une fonction interne privilégiée atteinte via un wrapper externe non protégé.
  • Un seul rôle d’administrateur pouvant réaliser toutes les opérations, avec un rayon d’attaque accru.
  • Une autorisation basée sur une adresse fournie par l’utilisateur plutôt que sur une mappage de rôle immuable ou contrôlé.
  • Des fonctions de mise à niveau qui valident l’origine de l’appel mais pas la nouvelle implémentation ou la voie de mise à niveau.

Ce schéma défectueux est particulièrement subtil :

function setOperator(address newOperator) external {
    operator = newOperator;
}

La fonction de paiement peut exiger correctement msg.sender == operator, mais la frontière de contrôle d’accès a déjà échoué, car n’importe qui peut se nommer opérateur. Les réviseurs devraient remonter le fil depuis chaque fonction sensible pour identifier qui peut acquérir le rôle requis.

La méthodologie Soken considère l’attribution de rôle et la configuration d’adresse de confiance comme des opérations privilégiées en soi. Une conception d’accès sécurisé protège autant la dernière étape du transfert d’actif que chaque chemin qui peut le débloquer.

À quoi ressemble une attaque de contrôle d’accès défectueux en code Solidity ?

Ce coffre vulnérable vérifie l’appelant avant une libération, mais sa fonction setPayoutOperator est publique. Un attaquant peut s’attribuer le rôle d’opérateur puis invoquer pay, rendant la vérification d’autorisation ultérieure sans effet.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract VulnerablePayoutVault {
    address public payoutOperator;

    constructor(address initialOperator) {
        payoutOperator = initialOperator;
    }

    receive() external payable {}

    function setPayoutOperator(address newOperator) external {
        // VULNÉRABLE : tout appelant peut s’octroyer l’autorité de paiement.
        payoutOperator = newOperator;
    }

    function pay(address payable recipient, uint256 amount) external {
        require(msg.sender == payoutOperator, "not operator");
        require(address(this).balance >= amount, "solde insuffisant");

        (bool sent, ) = recipient.call{value: amount}("");
        require(sent, "échec du paiement");
    }
}

La correction consiste à séparer l’administration des opérations. Seul le propriétaire peut attribuer ou révoquer le rôle limité de paiement, tandis qu’un opérateur de paiement ne peut ni gérer des rôles ni changer de propriétaire.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract FixedPayoutVault {
    address public owner;
    mapping(address => bool) public payoutOperators;

    modifier onlyOwner() {
        require(msg.sender == owner, "pas propriétaire");
        _;
    }

    modifier onlyPayoutOperator() {
        require(payoutOperators[msg.sender], "pas opérateur de paiement");
        _;
    }

    constructor(address initialOwner, address initialOperator) {
        require(initialOwner != address(0), "propriétaire zéro");
        require(initialOperator != address(0), "opérateur zéro");
        owner = initialOwner;
        payoutOperators[initialOperator] = true;
    }

    receive() external payable {}

    function setPayoutOperator(address operator, bool allowed) external onlyOwner {
        require(operator != address(0), "opérateur zéro");
        // CORRECTION : seul le propriétaire peut modifier l’appartenance au rôle privilégié.
        payoutOperators[operator] = allowed;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "propriétaire zéro");
        owner = newOwner;
    }

    function pay(address payable recipient, uint256 amount)
        external
        onlyPayoutOperator
    {
        require(address(this).balance >= amount, "solde insuffisant");
        (bool sent, ) = recipient.call{value: amount}("");
        require(sent, "échec du paiement");
    }
}

Si des changements de rôle, des permissions de mise à niveau ou des retraits de trésorerie existent dans votre système, une audit et évaluation de sécurité axée sur le contrôle d’accès peut tracer si chaque parcours menant à ces capacités est correctement contraint.

Comment les développeurs Solidity peuvent-ils prévenir les vulnérabilités de contrôle d’accès défectueux ?

Éviter de telles attaques requiert un modèle d’autorisation explicite, non pas des require dispersés ajoutés après la mise en place de la fonctionnalité. Définissez qui peut effectuer chaque opération sensible, pourquoi cette autorité existe, et quel rôle plus restreint peut effectuer en sécurité des tâches routinières.

Technique Ce qu’elle empêche Coût en gaz ou complexité Limitation
onlyOwner Administration non autorisée des fonctions réservées au propriétaire Un accès à la storage par appel Un propriétaire compromis possède toujours toute l’autorité
Mappages de rôles ou AccessControl d’OpenZeppelin Usage non autorisé de rôles opérationnels séparés Lecture de storage et logique de gestion des rôles Un mauvais design de rôles peut encore créer des privilèges excessifs
Rôles de moindre privilège Un opérateur utilisant des pouvoirs d’administration non liés Plus de fonctions et de définitions de rôles à maintenir Nécessite des frontières opérationnelles claires
Timelocks pour changements critiques Mises à jour malveillantes immédiates ou modifications de paramètres Exécution différée et contrats additionnels Ne prévient pas une action malveillante si les utilisateurs peuvent répondre après le délai
Administration contrôlée par multisig Un seul clé modifie unilatéralement l’état privilégié Coordination entre signataires et surcharge opérationnelle Signataires partagés ou politiques faibles restent risqués
Gardiens d’initiation unique Initialisation non autorisée ou répétée Un drapeau en storage et une logique d’initialisation La procédure de déploiement doit encore définir correctement l’autorité visée

Utilisez les primitives de propriété et de rôle, bien vérifiées, lorsque c’est pertinent, mais ne considérez pas une bibliothèque comme un modèle de sécurité complet. Par exemple, Ownable d’OpenZeppelin établit la mécanique de propriété, mais le protocole doit encore décider si cette propriété doit contrôler la mise en pause, les mises à niveau, la collecte de frais, le remplacement d’oracle ou la récupération d’urgence.

Évitez l’utilisation de tx.origin pour l’autorisation. Cela peut être influencé par des contrats intermédiaires et ne permet pas d’identifier l’appelant immédiat. Utilisez plutôt msg.sender, puis vérifiez-le par rapport à une règle d’autorisation définie (propriétaire, rôle, multisig ou spécifique au contrat).

Quels incidents réels montrent l’impact des attaques de contrôle d’accès ?

DefiLlama recense 130 incidents de smart-contracts classés comme « Contrôle d’accès inapproprié » de 2020 à 2026, hors violations d’échanges et de portefeuilles, pour des pertes cumulées de 909,7 millions de dollars. Les incidents vérifiés suivants montrent que de tels défauts impactent ponts, protocoles DeFi, bots et applications sur plusieurs chaînes.

Protocole Date Montant Chaîne
Poly Network 2021-08-10 611,0 M$ Ethereum, BSC, Polygon
Mirror 2021-10-08 90,0 M$ Terra
Wintermute 2022-06-05 17,6 M$ Optimism
ValueDefi 2021-05-05 10,0 M$ BSC
Cork V1 2025-05-28 12,0 M$ Ethereum
SUPERFORTUNE AI 2026-05-27 15,2 M$ Ethereum
The Sandbox 2026-08-22 675 K$ Base, BSC, Ethereum
Radix 2026-08-31 1,2 M$ Radix

Ces incidents partagent une leçon de sécurité fondamentale : la valeur économique d’un protocole peut être exposée lorsque la frontière d’autorité d’un contrat ne correspond pas à son modèle de confiance prévu. La fonction spécifique affectée peut varier, mais la classe de défaillance reste la même : un acteur obtient ou exerce une autorité que le design aurait dû lui refuser.

Comment détecter avant déploiement des vulnérabilités de contrôle d’accès défectueux ?

Détecter ces vulnérabilités nécessite de cartographier chaque changement d’état sensible à ses conditions d’autorisation, puis de prouver qu’aucun autre chemin d’appel ne peut y contourner ces conditions. Les outils automatisés aident à repérer des modèles suspects, mais une revue humaine est indispensable pour comprendre l’autorité métier, les hypothèses de confiance et les chaînes d’ascension des privilèges.

Utilisez cette checklist de vérification :

  1. Listez chaque fonction qui transfère des actifs, modifie des rôles, met à jour du code, met en pause une activité ou change des adresses de confiance.
  2. Identifiez l’appelant prévu et la règle d’autorisation pour chaque fonction.
  3. Tracez chaque fonction pouvant accorder, révoquer, transférer ou initialiser cette autorisation.
  4. Vérifiez tous les wrappers externes atteignant des fonctions internes privilégiées.
  5. Confirmez que l’initialisation ne peut se produire qu’une seule fois et dans le cadre du processus prévu.
  6. Vérifiez que les rôles d’urgence ne peuvent pas réaliser des actions routinières ou de gouvernance non liées.
  7. Testez la révocation de rôle, le transfert de propriété, le traitement des adresses zero.
  8. Examinez proxies, contrats hérités et chemins delegatecall dans la même zone d’autorité.

Slither peut signaler les pattern manquants d’autorisation, une utilisation dangereuse de tx.origin, ou des modificateurs incohérents, mais ses résultats nécessitent une validation manuelle. La fuzzing avec Foundry est utile pour générer des appels depuis des adresses arbitraires et vérifier que des appelants non autorisés ne peuvent modifier l’état privilégié.

Les tests d’invariants sont particulièrement efficaces : par exemple, une invariant de vault peut stipuler qu’une adresse sans rôle de paiement ne doit jamais faire diminuer le solde du vault via pay. Une autre invariant pourrait assurer que seul le propriétaire peut modifier l’appartenance à un rôle. Ces properties testent directement la règle de sécurité, plutôt que de se limiter à tester des parcours utilisateur attendus.

Alerte sécurité, méthodologie Soken : Examinez l’autorisation comme un graphe de capacités, et pas comme une liste de modificateurs. Toute sortie privilégiée doit avoir un chemin délibéré et auditable vers l’autorité, et chaque chemin non prévu constitue une voie potentielle d’ascension.

Les attaques de contrôle d’accès défectueux sont évitées en rendant l’autorité explicite, en limitant les pouvoirs de chaque rôle, et en protégeant soigneusement les fonctions qui confèrent ces pouvoirs aussi bien que celles qui déplacent des fonds. Une fonction de retrait correctement restreinte n’est pas sécurisée si n’importe qui peut désigner le retrayeur.

Commencez par élaborer une carte de l’autorité pour vos contrats déployés ou prévus, y compris proxies et comptes opérationnels, puis convertissez chaque règle en un modificateur et un test invariant. Pour approfondir la recherche en sécurité Solidity et autres classes de vulnérabilités adjacentes, explorez le Soken Hub.

Auteur de l’article

Questions fréquemment posées

Qu'est-ce qu'une vulnérabilité de Broken Access Control en Solidity ?

Une vulnérabilité de Broken Access Control en Solidity survient lorsqu'un contrat ne confirme pas de manière fiable si msg.sender est autorisé pour une action privilégiée. La faille permet à un appelant non fiable d'accéder à des capacités de changement d'état, menant potentiellement à une escalade de privilèges.

Comment les attaques Broken Access Control mènent-elles à une escalade de privilèges ?

Ces attaques permettent à un appelant non fiable d'accéder à une capacité réservée à un utilisateur autorisé, lui permettant d'effectuer des actions comme transférer des actifs ou modifier des contrats, compromettant la sécurité des fonds ou la gouvernance.

Quelles actions Solidity doivent être restreintes par le contrôle d'accès ?

Le contrôle d'accès doit limiter toutes les capacités de changement d'état non destinées à des appelants arbitraires, telles que transférer des actifs, modifier les contrats ou attribuer des rôles, en reliant chaque capacité à une vérification d'autorisation fiable.

Que s'est-il passé lors de l'incident Poly Network concernant le contrôle d'accès ?

Le 10 août 2021, Poly Network a perdu 611 millions de dollars en Ethereum, BSC et Polygon suite à un incident classé par DefiLlama comme un problème de Contrôle d'Accès Inadéquat, montrant l'importance de la vérification d'autorisation pour la sécurité.

Pourquoi l'autorisation est-elle une frontière essentielle de sécurité en Solidity ?

L'autorisation est cruciale car elle empêche un appelant non fiable d'accéder à des fonctionnalités sensibles controlant des fonds ou configurations de gouvernance, évitant que des failles d'autorisation ne deviennent des risques de sécurité majeurs.

Chat