Les attaques par validation d’entrée manquante se produisent lorsqu’un contrat Solidity accepte des adresses, montants, identifiants, prix ou valeurs de configuration contrôlés par l’utilisateur, sans prouver qu’ils satisfont aux hypothèses économiques et de sécurité du contrat. Pour les prévenir, validez chaque entrée externe à la frontière de confiance avec des listes d’autorisation, des vérifications de limites, des contrôles d’adresses zéro, des mécanismes d’autorisation et des invariants d’état avant que des actifs ne soient déplacés ou que des changements comptables ne soient effectués.
L’impact est concret : Qubit a perdu 80,0 millions de dollars sur BSC le 28-01-2022 lors d’un incident classé comme validation d’entrée manquante. Ces défaillances ne sont pas simplement liées à des transactions malformées. Une valeur non vérifiée peut faire exécuter un code Solidity valide une action commerciale invalide, comme créditer une garantie inexistante, transférer des actifs vers une destination non fiable ou créer une dette dépassant la limite réelle d’un emprunteur.
Qu’est-ce qu’une vulnérabilité de validation d’entrée en Solidity, et comment fonctionne une attaque ?
Une vulnérabilité de validation d’entrée en Solidity existe lorsqu’un contrat considère une valeur fournie par l’appelant comme fiable, alors même que cette valeur contrôle des permissions, la comptabilité des actifs, la tarification ou les transitions d’état. Un attaquant fournit une valeur syntaxiquement valide pour l’ABI, mais économiquement invalide pour le protocole, puis utilise l’état résultant pour retirer des actifs, créer des revendications ou contourner des restrictions.
Solidity impose les types, pas l’intention du protocole. Un uint256 peut être n’importe quel nombre ne provoquant pas de revert, et une address peut représenter une EOA, un contrat malveillant, l’adresse zéro ou un jeton non supporté. Le contrat doit faire respecter les règles sémantiques manquantes.
Une séquence d’attaque typique est la suivante :
- L’attaquant identifie une fonction publique acceptant un montant, jeton, destinataire, prix ou identifiant non vérifié.
- Il choisit une valeur qui viole l’hypothèse prévue par le protocole.
- Le contrat enregistre un solde, droit, limite de dette ou état d’autorisation non sécurisé.
- L’attaquant appelle une fonction ultérieure qui fait confiance à cet état corrompu.
- Le protocole transfère des actifs ou crée une revendication transférable basée sur cet état falsifié.
- D’autres utilisateurs supportent la perte lorsque le déficit comptable devient visible.
La question critique est : quel fait cette entrée prétend-elle représenter, et où le contrat vérifie-t-il indépendamment ce fait ? Si la réponse est “l’appelant l’a fourni”, le contrat présente une vulnérabilité d’impropre validation d’entrée.
Quels modèles Solidity causent des attaques de validation d’entrée ?
Les attaques de validation d’entrée surviennent généralement lorsqu’un contrat accepte une certaine flexibilité pour mieux s’intégrer ou améliorer l’expérience utilisateur, sans limiter cette flexibilité à la gamme supportée par le protocole. Le paramètre dangereux n’est pas toujours nommé price ou amount ; il peut s’agir d’une adresse de jeton, d’un parcours, d’un destinataire, d’un ID de tranche, d’une preuve ou d’une adresse de contrat externe.
Les modèles non sécurisés courants incluent :
- Valeur de garantie déclarée par l’appelant : un emprunteur fournit la valeur utilisée pour calculer son pouvoir d’emprunt.
- Adresses de jetons arbitraires : une fonction de dépôt accepte n’importe quel ERC-20, y compris ceux avec un comportement de transfert non standard.
- Adresses de destinataires non vérifiées : une fonction accepte
address(0), le contrat lui-même ou une adresse qui viole les règles de garde. - Pourcentages et ratios non limités : une commission, LTV, remise, glissement ou taux de récompense dépasse la plage prévue en points de base.
- Relations de tableaux non validées : des tableaux parallèles de longueurs différentes, des doublons acceptés ou un parcours répétant un actif de façon inattendue.
- Transitions d’état sans préconditions : une revendication, annulation, rachat ou migration s’effectue dans un mauvais état du cycle de vie.
- Données externes acceptées comme vérités : une fonction fait confiance à des prix, soldes, signatures ou valeurs de retour fournies par l’appelant, sans vérification.
La validation doit être contextuelle. require(amount > 0) est utile, mais ne prouve pas que amount est abordable, garanti, dans une limite ou cohérent avec l’état actuel. De même, vérifier qu’une adresse de jeton n’est pas zéro ne garantit pas que le jeton est approuvé, qu’il se comporte comme prévu ou qu’il appartient au marché adéquat.
La méthodologie de revue de Soken traite chaque paramètre externe comme une tentative de transition d’état : identifier l’invariant qu’il peut affecter, puis vérifier que la fonction en assure le respect avant d’écrire dans le stockage.
À quoi ressemble un exemple d’attaque de validation d’entrée en Solidity ?
Cet exemple d’attaque de validation d’entrée permet à un emprunteur de soumettre sa propre collateralValue. Le contrat vérifie que le prêt demandé ne dépasse pas cette valeur, mais n’établit jamais que l’emprunteur a déposé une garantie d’une valeur équivalente. Un appelant peut donc définir les deux valeurs arbitrairement et emprunter ainsi des actifs financés par le prêteur.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
}
contract UnsafeLender {
IERC20 public immutable asset;
mapping(address => uint256) public debt;
constructor(IERC20 asset_) {
require(address(asset_) != address(0), "zero asset");
asset = asset_;
}
function borrow(
uint256 collateralValue,
uint256 amount
) external {
require(collateralValue > 0, "no collateral");
require(amount <= collateralValue, "insufficient collateral");
// VULNÉRABLE : l’emprunteur fournit la valeur de garantie du prêt.
debt[msg.sender] += amount;
require(asset.transfer(msg.sender, amount), "transfer failed");
}
}
La conception corrigée dérive la capacité d’emprunt des garanties réellement reçues par le contrat. Les adresses d’actifs immuables servent de listes blanches limitées, les vérifications dans le constructeur rejettent les adresses zero, le LTV est borné, et la vérification du delta de solde valide l’invariant de transfert plutôt que de faire confiance au montant déposé demandé.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
contract ValidatedLender {
IERC20 public immutable loanAsset;
IERC20 public immutable collateralAsset;
uint256 public immutable maxLtvBps;
mapping(address => uint256) public collateral;
mapping(address => uint256) public debt;
constructor(IERC20 loanAsset_, IERC20 collateralAsset_, uint256 maxLtvBps_) {
require(address(loanAsset_) != address(0), "zero loan asset");
require(address(collateralAsset_) != address(0), "zero collateral asset");
require(maxLtvBps_ > 0 && maxLtvBps_ <= 10_000, "invalid LTV");
loanAsset = loanAsset_;
collateralAsset = collateralAsset_;
maxLtvBps = maxLtvBps_;
}
function depositCollateral(uint256 amount) external {
require(amount > 0, "zero amount");
uint256 beforeBalance = collateralAsset.balanceOf(address(this));
require(collateralAsset.transferFrom(msg.sender, address(this), amount), "transfer failed");
uint256 received = collateralAsset.balanceOf(address(this)) - beforeBalance;
// RÉGLAGE : ne comptabilise que la garantie réellement reçue.
require(received == amount, "comportement de jeton non supporté");
collateral[msg.sender] += received;
}
function borrow(uint256 amount) external {
require(amount > 0, "zero amount");
uint256 limit = (collateral[msg.sender] * maxLtvBps) / 10_000;
require(debt[msg.sender] + amount <= limit, "limite de prêt dépassée");
debt[msg.sender] += amount;
require(loanAsset.transfer(msg.sender, amount), "transfert échoué");
}
}
Si votre protocole accepte des actifs, parcours ou entrées comptables flexibles, une audit de smart-contracts adversarial et des tests d’intrusion peuvent retracer si chaque valeur revendiquée est établie de façon indépendante avant d’affecter des fonds.
Comment prévenir les attaques de validation d’entrée en Solidity ?
Pour prévenir ces attaques, il faut valider les entrées dès leur entrée dans le contrat, puis conserver ces hypothèses à l’aide d’invariants explicites tout au long du cycle de vie du contrat. La meilleure défense n’est pas un simple modificateur, mais une conception dans laquelle chaque changement d’état significatif dispose d’un ensemble clairement défini d’entrées autorisées et d’une condition vérifiable restant vraie après.
| Technique | Ce qu’elle empêche | Coût en gaz ou complexité | Limitation |
|---|---|---|---|
| Listes blanches de jetons et d’implémentation | ERC-20 non supportés, adaptateurs malveillants, appels externes arbitraires | Recherche en stockage ou comparaison d’adresses immuables | Les jetons approuvés peuvent toujours introduire un risque économique |
| Vérifications de limites | Montants zéro, taux excessifs, points de base invalides, délais non sécurisés | Peu coûteux, généralement quelques comparaisons | Les limites seules ne garantissent pas la correction économique |
| Vérification d’adresses zéro et de destinataires | Brûlures d’actifs, propriété inutilisable, destinations de garde invalides | Faible, une comparaison d’adresse | Une adresse non zéro reste non fiable si malveillante |
| Vérifications d’autorisation et de cycle de vie | Appels dans un mauvais état ou par un mauvais acteur | Lecture en stockage et conception des rôles | Nécessitent une administration sécurisée |
| Validation du delta de solde | Crédits de dépôt faux et comportement non standard des jetons | Lectures supplémentaires du solde des jetons | Nécessite une gestion prudente des actifs en rebasing |
| Validation d’invariants | Solvabilité, relations d’offre, dette et réserves cassées | Lectures et opérations arithmétiques supplémentaires | Les invariants doivent couvrir tout le modèle économique |
Pour les protocoles configurables, validez la configuration à la fois lors de sa création et lors de son utilisation. Une parameterisation définie par la gouvernance peut être sûre à la création, mais devenir dangereuse si une dépendance change. Pour des systèmes modulaires, validez avec rigueur les paramètres d’initialisation comme ceux des fonctions publiques.
Aperçu sécurité : La méthodologie de Soken commence par l’invariant, pas par le
require. Si les revues ne peuvent pas formuler ce qui doit rester vrai après qu’une entrée contrôlée par l’utilisateur a été traitée, elles ne peuvent pas déterminer si la validation est suffisante.
Quelles incidents réels ont été classés comme validation d’entrée manquante ?
La validation d’entrée manquante a été liée à des pertes massives de contrats intelligents dans les domaines du prêt, de l’échange et de la gestion d’actifs. La catégorie d’incidents couvre différentes blockchains et architectures, montrant que l’erreur vient d’une défaillance de la logique du protocole plutôt que d’une erreur spécifique à Solidity sur le réseau.
| Protocole | Date | Montant perdu | Chaîne |
|---|---|---|---|
| Qubit | 2022-01-28 | 80,0 M$ | BSC |
| Hedgey | 2024-04-19 | 44,7 M$ | Arbitrum, Ethereum |
| Transit Swap | 2022-10-02 | 28,9 M$ | Ethereum, BSC |
| Pickle | 2020-11-22 | 19,7 M$ | Ethereum |
Ces incidents partagent une leçon centrale : les contrats ne peuvent faire confiance à la vérité du protocole simplement parce qu’une transaction l’a encodée et passée avec succès. Les systèmes d’actifs ont besoin d’une validation sémantique autour de la propriété précise qui protège la valeur, qu’il s’agisse de la garantie reçue, d’un actif supporté, d’un parcours autorisé ou d’une transition valide du cycle de vie.
DefiLlama répertorie 52 incidents de contrats intelligents de 2020 à 2026 sous la catégorie Validation d’entrée manquante, avec des pertes combinées de 330,5 millions de dollars. Cette ampleur fait que Soken considère la revue de validation comme un exercice d’économie de sécurité, et non une simple liste de vérifications superficielles.
Comment détecter les vulnérabilités de validation d’entrée incorrecte ?
Vous pouvez détecter ces vulnérabilités en retraçant chaque paramètre accessible extérieurement vers ses écritures en mémoire, ses appels externes et ses transferts d’actifs, puis en testant si des valeurs invalides mais valides en ABI peuvent violer un invariant. L’analyse statique détecte certains contrôles manquants, mais des tests scénarisés s’avèrent nécessaires pour les hypothèses spécifiques des protocoles.
Utilisez cette liste de contrôle pour votre revue :
- Dressez la liste de chaque paramètre
externaloupublicinfluençant une valeur, une autorité ou un routage. - Vérifiez si chaque adresse est non nulle, approuvée et appropriée pour son rôle.
- Contrôlez chaque montant par rapport aux plafonds du protocole, aux soldes, aux limites de dette, aux plafonds d’offre et aux décimales.
- Confirmez que prix, valeurs de garantie et soldes proviennent de sources fiables ou vérifiées indépendamment.
- Validez la cohérence des longueurs de tableaux, leur unicité, leur ordre et leurs relations d’index entre tableaux.
- Assurez-vous que chaque fonction ne permet que des états de cycle de vie valides et des appelants autorisés.
- Testez la comptabilisation du delta de solde pour les frais à la transfert, le rebasing et les comportements non standards d’ERC-20.
- Spécifiez et testez la post-condition pour chaque opération de mouvement d’actifs.
Slither peut repérer des modèles tels que des appels externes dangereux, des valeurs de retour non vérifiées, ou des contrôles d’accès faibles, souvent liés aux échecs de validation. Il ne peut pas déduire tous les invariants du protocole, d’où l’intérêt d’utiliser Foundry fuzzing pour générer des valeurs nulles, maximales, des tableaux dupliqués, adresses inattendues ou jetons non supportés.
Les tests d’invariants sont particulièrement efficaces, par exemple vérifier que la dette totale ne dépasse jamais la capacité d’emprunt configurée, que la garantie créditée ne dépasse jamais les actifs reçus et que seuls des actifs approuvés peuvent modifier la comptabilité du marché. Explorez le Soken Hub lorsque vous construisez un processus global de revue de sécurité autour de ces propriétés.
La validation d’entrée constitue la limite entre l’intention de l’utilisateur et la vérité du protocole. Un contrat ne doit jamais créditer, autoriser, tarifer ou transférer uniquement en fonction de ce que le demandeur affirme. Il faut dériver des valeurs sensibles à partir d’un état vérifié, limiter les domaines acceptés et faire respecter l’invariant qui garantit la sécurité économique de l’état résultant.
L’étape pratique suivante consiste à créer une carte paramètre-invariant pour chaque fonction de déplacement d’actifs dans votre code. Tout paramètre sans source de confiance documentée, plage autorisée et post-condition mérite une révision immédiate.