Beosin révèle une vulnérabilité critique dans l'instruction IDL du framework Solana Anchor en version ancienne

iconMetaEra
Partager
AI summary iconRésumé
L'équipe de sécurité de Beosin a identifié une vulnérabilité critique dans les instructions IDL des frameworks Solana Anchor de version ancienne. Cette faille permet aux attaquants de s'emparer des comptes PDA détenus par un programme en exploitant les déclarations AccountInfo, permettant un vol de fonds en deux étapes sans privilèges utilisateur. L'incident souligne la nécessité d'un cadre de conformité plus robuste dans le développement de contrats intelligents. Avec l'approche du MiCA (Réglementation européenne sur les actifs cryptographiques), de telles vulnérabilités mettent en lumière l'importance des audits de sécurité en temps réel et du respect des normes réglementaires.

Anchor est le cadre de développement le plus dominant dans l'écosystème Solana. Il réduit considérablement la courbe d'apprentissage grâce à des fonctionnalités telles que la validation déclarative des comptes, la sérialisation automatique et les vérifications de sécurité intégrées. Toutefois, tout en offrant une grande commodité, le cadre injecte discrètement dans chaque programme certaines instructions internes que les développeurs peuvent ne pas connaître. Ces instructions « cachées » peuvent être exploitées par des attaquants dans des conditions spécifiques, entraînant des pertes financières graves.

L'équipe de sécurité de Beosin révèle un modèle de vulnérabilité critique : dans les versions anciennes d'Anchor, lorsqu'un développeur utilise AccountInfo pour déclarer un compte PDA détenu par le programme, un attaquant peut, via l'instruction IDL injectée automatiquement par Anchor, s'emparer du contrôle du compte et vider tous les SOL qu'il contient en seulement deux étapes, sans nécessiter aucun privilège.

I. Analyse des instructions IDL et des mécanismes associés

1.1 Instruction IDL

Anchor injecte par défaut un ensemble d'instructions de gestion IDL (Interface Definition Language) dans chaque programme, sauf si le feature no-idl est explicitement activé lors de la construction. Ces instructions incluent :

IdlCreateAccount : créer un compte IDL sur la chaîne

IdlWrite : Écrire des données dans un compte IDL / un compte tampon

IdlSetAuthority : modifier l'autorité du compte IDL

IdlCloseAccount : ferme le compte IDL et transfère tous les lamports au destinataire désigné

IdlResizeAccount : Ajuster la taille du compte IDL

IdlCreateBuffer : créer un compte de tampon IDL (IDL Buffer)

IdlSetBuffer : Remplace le compte IDL officiel avec les données du compte tampon

Ces instructions ont été conçues à l'origine pour la gestion IDL sur chaîne, mais elles possèdent des capacités opérationnelles spéciales sur les comptes détenus par le programme (lecture/écriture de données, modification du contrôleur, fermeture et transfert de lamports) — c'est précisément là que réside la vulnérabilité. L'attaquant n'a pas besoin d'appeler n'importe quelle instruction métier écrite par le développeur ; il peut appeler directement ces instructions intégrées.

1.2 Compte tampon IDL

Le compte tampon IDL (IDL Buffer) est un compte temporaire introduit par Anchor pour télécharger des données IDL volumineuses par segments. Étant donné que l'IDL complet (après compression JSON) peut dépasser la limite de taille d'une seule transaction, Anchor permet de créer d'abord un tampon via IdlCreateBuffer, puis d'écrire les données par lots à l'aide de plusieurs instructions IdlWrite, avant de soumettre l'ensemble en une seule opération via IdlSetBuffer.

Le point clé réside dans la structure des données du compte IDL / compte tampon : il commence par une en-tête à disposition fixe contenant un champ authority: Pubkey (appelé controller dans les résultats de test de ce document). Les instructions IDL utilisent ce champ pour déterminer « qui a le droit d'opérer sur ce compte ».

Le problème réside dans le fait que la logique de traitement d’IdlCreateBuffer dans les versions antérieures d’Anchor initialise tout compte transmis, possédé par le programme lui-même, en tant que compte tampon, et définit directement l’autorité comme étant le signataire de la transaction. Autrement dit, dès qu’un compte a pour propriétaire le programme (par exemple, un PDA de trésorerie du programme), un attaquant peut s’assigner lui-même en tant que contrôleur, puis utiliser IdlCloseAccount pour transférer légitimement tous les SOL contenus dans le compte.

1.3 Conditions de déclenchement de la vulnérabilité

L'exploitation de cette vulnérabilité nécessite la satisfaction simultanée des conditions suivantes :

  • Utiliser une version ancienne d'Anchor : l'instruction IDL mal définie ne vérifie pas suffisamment les comptes, permettant de traiter accidentellement un compte métier comme un compte IDL.
  • no-idl non activé : le programme conserve l'entrée d'instruction IDL injectée par défaut lors de la construction, exposant ainsi la surface d'attaque.
  • Déclarer le PDA détenu par le programme avec AccountInfo : les développeurs utilisent AccountInfo brut pour charger des comptes de fonds (par exemple, un PDA de trésorerie), manquant ainsi le discriminateur / propriétaire fourni par les comptes typés Anchor (comme Account), ce qui rend ce compte indiscernable des comptes IDL selon les instructions IDL.
  • Le compte est détenu par le programme et possède des lamports : owner == ce programme est une condition préalable pour que les instructions IDL puissent le manipuler ; il ne présente une valeur à vider que s'il détient des SOL.

Après avoir satisfait aux conditions ci-dessus, l'attaquant n'a besoin que de deux transactions ordinaires : d'abord IdlCreateBuffer pour s'emparer du controller, puis IdlCloseAccount pour transférer tous les SOL, réalisant ainsi l'attaque sans que le programme cible n'accorde aucun autorisation.

1.4 Chaîne d'attaque

En combinant l'implémentation interne d'Anchor, décomposez progressivement comment l'attaquant n'utilise que deux instructions intégrées pour « dissimuler » un simple vault de fonds en tant que compte IDL et le vider.

Aperçu du processus d'attaque

Étape 1 : IdlCreateBuffer(treasury, signer = attacker)    
└─ treasury.controller ==> attacker (devient contrôleur)  
Étape 2 : IdlCloseAccount(treasury, authority = attacker, dest = attacker)  
  └─ treasury.lamports ==> attacker (vidage du trésor)

Étape 1 : IdlCreateBuffer prend le contrôle de l'autorité

Anchor implémente internement environ comme suit :

#[derive(Accounts)]pub struct IdlCreateBuffer {  
#[account(zero)]          // ← Key: un compte avec son discriminateur entièrement à 0  pub buffer: Account,  
pub authority: Signer,}    
pub fn idl_create_buffer(ctx: Context) -> Result 
{ 
 let idl = &mut ctx.accounts.buffer;  idl.authority = *ctx.accounts.authority.key; // définir l'autorité comme autorité  Ok(())}

Le terme #[account(zero)] signifie : accepter un compte dont le discriminator est entièrement nul et qui est détenu par le programme, comme un compte IDL non initialisé à initialiser. Le vault satisfait exactement ces deux conditions :

État du vault

Conditions

détenue par le programme

Après init_if_needed, le propriétaire est défini sur ce programme.

discriminator tous nuls

Utilisez le type AccountInfo, Anchor n'écrit pas le discriminator, les données sont toutes nulles.

L'attaquant transmet ensuite le vault à IdlCreateBuffer :

  • Anchor écrit le discriminator d'IdlAccount dans les 8 premiers octets du vault ;
  • Écrire la clé publique de l'attaquant dans le champ authority.

Ainsi, le vault a été « falsifié » en un IdlAccount dont l'autorité est l'attaquant — tandis que ses SOL internes n'ont pas été touchés.

Étape 2 : IdlCloseAccount — Vider les fonds

#[derive(Accounts)]pub struct IdlCloseAccount {  #[account(mut, has_one = authority)]  // ← check authority == signer  pub account: Account,  pub authority: Signer,  #[account(mut)]  pub destination: AccountInfo,  // ← attack wallet}

Le vault est désormais entièrement vérifié :

  • discriminator correspond à IdlAccount
  • Le champ authority = clé publique de l'attaquant
  • has_one = authority vérifié

Ainsi, tous les lamports contenus dans le vault (l'ensemble du SOL déposé par l'utilisateur) ont été légalement transférés vers le compte de l'attaquant, réduisant le vault à zéro.

Cause fondamentale :

L'utilisation de AccountInfo dans deposit.rs au lieu d'un compte Anchor typé est la cause fatale de cette vulnérabilité :

(1) Les comptes typés (comme Account) écrivent lors de leur initialisation un discriminator de 8 octets spécifique à cette structure ;

(2) Une fois le propre discriminator écrit, Anchor ne peut plus le traiter comme un IdlAccount (discriminator incohérent), et la première étape IdlCreateBuffer échoue, rompant ainsi la chaîne d'attaque.

Deuxièmement, analyse de cas

Les tests suivants sont basés sur le contrat PoC de pont construit avec Anchor 0.31.0. Les tests simulent un véritable trésor de pont (Bridge Treasury), dont le propriétaire est le programme de pont lui-même, contenant 1,001281 SOL déposés par les utilisateurs. Le portefeuille de l'attaquant contient initialement 2 SOL et ne possède aucun privilège.

Project

Valeur / Description

Programme de pont

CJutNlr8d3oxdb11ReOLaZd5jPsqUxgwt2mv5e7equtE

Compte trésorerie (Treasury)

DxGkzaMhP5Wy824GhoehErdD6MudfuEPGPwaV2s77FsM

Propriétaire de KuCoin

Programme propriétaire de PDA

Solde initial du coffre

1,001281 SOL (fonds déposés par l'utilisateur)

Contrôleur initial

0x0000...0000 (tous zéros, non configuré)

Solde initial du portefeuille de l'attaquant

2.000000 SOL

Droits requis

AUCUN (aucune autorisation requise)

Nombre de transactions

2 ordres

Solde final du coffre

0,000000 SOL (vidé)

L'attaquant a réalisé un profit

+1.001281 SOL

Capture d'écran de l'exécution du PoC (tests/poc-idl-hijack.ts) :

https://wdcdn.qpic.cn/MTMxMDI3MDE1MTgxMDU0NzA_812435_a37-yrU_dY02i1_I_1785900257?w=1080&h=1287

On peut voir que IdlCreateBuffer à l’étape 1 définit l’attaquant comme contrôleur du coffre (le solde reste inchangé) ; IdlCloseAccount à l’étape 2 transfère les 1,001281 SOL du coffre intégralement au portefeuille de l’attaquant, réduisant le solde du coffre à zéro.

Recommandations de correction/protection :

(1) Mettez à jour la version d'Anchor : la version la plus récente corrige ce problème (les instructions IDL distinguent strictement les comptes IDL des comptes métier) ; utiliser une version ancienne d'Anchor est le moyen le plus direct et le plus fondamental de se protéger.

(2) Activer no-idl lors de la construction : désactiver explicitement l'injection d'instructions IDL pour les applications en production, éliminant ainsi la surface d'attaque à la source.

(3) Utilisez des comptes typés au lieu de AccountInfo brut : utilisez des comptes typés tels que Account / SystemAccount avec vérification du discriminator et du propriétaire pour héberger les comptes de fonds, afin qu'ils ne puissent pas être mal interprétés par les instructions IDL.

(4) Minimiser les comptes pouvant être vidés par le programme : appliquer des contraintes explicites sur le propriétaire / les seeds / le discriminateur pour les PDA stockant des fonds, et vérifier le discriminateur du compte dans les instructions critiques.

Conclusion

Ce vulnérabilité est essentiellement une combinaison de « instructions masquées par le cadre + absence de vérification du type de compte ». Les développeurs doivent reconnaître que les instructions IDL injectées par défaut par Anchor constituent une surface d'attaque réelle ; mettre à jour la version du cadre et appliquer des contraintes de type fort aux comptes de fonds permet d'éliminer efficacement ce risque de vidange non autorisée de fonds.

Beosin est une entreprise leader en matière de sécurité blockchain et de conformité réglementaire, spécialisée dans l'audit de sécurité des contrats intelligents avant le lancement de projets, la surveillance et la blocage des risques sécuritaires pendant l'exploitation, le recouvrement d'actifs volés, la lutte contre le blanchiment d'argent (AML) et l'investigation. Beosin fournit des produits de conformité blockchain et des services de sécurité « tout-en-un » à plus de 200 fournisseurs d'actifs virtuels, ainsi qu'à 4 500 projets Web3 dans plus de 20 pays et régions à travers le monde. N'hésitez pas à nous contacter via la boîte de messages de notre compte officiel.

Clause de non-responsabilité : les informations sur cette page peuvent avoir été obtenues auprès de tiers et ne reflètent pas nécessairement les points de vue ou opinions de KuCoin. Ce contenu est fourni à titre informatif uniquement, sans aucune représentation ou garantie d’aucune sorte, et ne doit pas être interprété comme un conseil en investissement. KuCoin ne sera pas responsable des erreurs ou omissions, ni des résultats résultant de l’utilisation de ces informations. Les investissements dans les actifs numériques peuvent être risqués. Veuillez évaluer soigneusement les risques d’un produit et votre tolérance au risque en fonction de votre propre situation financière. Pour plus d’informations, veuillez consulter nos conditions d’utilisation et divulgation des risques.