Les développeurs s'appuyant sur IBC et les ingénieurs de sécurité examinant les intégrations IBC doivent examiner attentivement la surface d'attaque exposée aux clients ou canaux IBC malveillants.
Écrit par : Will
Avant-propos : Cet article décrit une vulnérabilité que Jump Crypto a découverte dans le programme de parachutage Stride : Stride est une chaîne Cosmos utilisée pour le jalonnement liquide dans l'écosystème Cosmos. Ce problème pourrait permettre à un attaquant de voler tous les parachutages non réclamés sur Stride. Au moment de la découverte, plus de 1,6 million de MSTR (équivalant à environ 4 millions de dollars) étaient à risque. Jump a signalé en privé la vulnérabilité aux contributeurs de Stride, et le problème a depuis été résolu, sans qu'aucun exploit malveillant ne se produise à la suite de nos efforts. Adresse du lien d'origine [1]
Airdrop sur la foulée
Stride effectue régulièrement de grands parachutages de son jeton natif [$STRD] pour encourager l'activité du réseau et décentraliser la gouvernance sur un large groupe de parties. Le code pour attribuer et réclamer des parachutages est à x/claim [2] implémenté dans le module. L'allocation Airdrop se fait via LoadAllocationData [3] défini par une fonction qui charge un fichier d'allocation contenant des adresses et des allocations de parachutage. Pour la plupart des parachutages, les adresses chargées décrivent les utilisateurs sur d'autres chaînes Cosmos, telles que Osmosis ou Juno, de sorte que le code les convertit d'abord en adresses Stride à l'aide de la fonction utils.ConvertAddressToStrideAddress.
Pour chaque compte dans l'airdrop, le module crée un ClaimRecord [4] , qui contient l'identifiant de largage pour un largage particulier, l'adresse convertie et le nombre de jetons alloués à l'utilisateur. Après avoir créé un ClaimRecord, un utilisateur avec l'adresse Stride correspondante peut envoyer un MsgClaimFreeAmount à la chaîne [5] Venez réclamer leur airdrop.
Cependant, cette implémentation n'a pas fonctionné lors du récent airdrop d'EVMOS car la fonction utils.ConvertAddressToStrideAddress mappe les adresses Evmos aux adresses Stride qui ne sont pas accessibles. En effet, les adresses EVMOS sont dérivées à l'aide du type de pièce 60, tandis que les adresses Stride sont dérivées à l'aide du type de pièce 118.
Afin que les utilisateurs concernés puissent toujours réclamer le largage aérien, l'équipe a ajouté la possibilité de mettre à jour l'adresse cible d'un ClaimRecord non réclamé via un message IBC inter-chaînes du compte EVMOS correspondant. Ce mécanisme de mise à jour est implémenté dans le cadre du module x/autopilot. x/pilote automatique [6] Intercepter une transmission IBC ICS-20 entrante et tenter d'extraire des instructions spécifiques à Stride de son champ mémo ou récepteur (le champ récepteur sert également de champ mémo dans les versions IBC antérieures à la v5) :
func(imIBCModule)OnRecvPacket(
ctxsdk.Context,
packetchanneltypes.Packet,
relayersdk.AccAddress,
)ibcexported.Accusé de réception{
//REMARQUE : l'accusé de réception sera écrit de manière synchrone lors de l'exécution du gestionnaire IBC.
La fonction convertit l'adresse du paquet IBC de l'expéditeur en une adresse Stride nommée senderStrideAddress et extrait l'airdropId et la nouvelle adresse de largage newStrideAddress des métadonnées du paquet. Il appelle ensuite UpdateAirdropAddress pour mettre à jour un ClaimRecord ouvert qui correspond à la combinaison de senderStrideAddress et airdropId à la nouvelle adresse.
Avec la mise à jour de ClaimRecord, newStrideAddress peut désormais réclamer le largage. Il est important de noter que ce mécanisme de mise à jour n'est protégé que par l'adresse de l'expéditeur spécifiée dans le paquet IBC. Stride n'effectue aucune autre vérification pour s'assurer que les mises à jour des parachutages ont été déclenchées par de vrais destinataires.
Pour comprendre pourquoi il s'agit d'une grave vulnérabilité, nous devons examiner de plus près IBC, le protocole de communication inter-blockchain.
Sécurité IBC
IBC est un mécanisme de communication inter-chaînes basé sur un client léger. Semblable aux protocoles réseau classiques, le module IBC central résume de nombreux détails de bas niveau, permettant aux développeurs de créer facilement leurs propres intégrations par-dessus. La connexion d'une chaîne compatible IBC (chaîne A) à une autre chaîne compatible IBC (chaîne B) ressemble un peu à ceci :
Création d'un client de machine solo sur une chaîne activée par IBC [ID client = 06-solomachine-6]
Création du clienttendermint sur la machine solo[ClientID=07-tendermint-M48f]
Canal confirmé sur une machine solo[ChannelID=channel-wwl6]
Connection établie!
Dans un premier temps, un client léger IBC de la chaîne A est créé sur la chaîne B, et inversement.Le client IBC est identifié de manière unique par son ID client, qui est utilisé pour suivre et vérifier l'état de la chaîne distante. Une fois les clients créés, ils peuvent se connecter via une connexion, qui est initiée par une poignée de main à quatre voies. Cela crée un ConnectionEnd sur la chaîne A avec les clients légers de la chaîne B sur A, et un autre sur la chaîne B avec les clients légers de la chaîne A sur B. Les connexions, une fois créées, sont persistantes et chiffrées par deux clients légers.
La communication sur les connexions est également divisée en différents canaux. Les canaux sont identifiés par la connexion sous-jacente et les ports source et de destination. Chaque port identifie un module sur la chaîne correspondante connectée via l'IBC. Le ChannelEnd associé à la connexion est créé sur les deux chaînes et identifié par channel-id. Les données peuvent maintenant être transférées entre les deux chaînes via le canal établi.
Il est important de se rappeler que IBC est un protocole sans autorisation par défaut. Cela signifie que n'importe qui peut connecter deux chaînes compatibles IBC sans autorisation ou approbation préalable. En fait, IBC prend en charge les soi-disant Solo Machines [7] Standard, un client ne représente pas une blockchain, mais un seul hôte ou machine. Étant donné que le contenu du paquet IBC est entièrement contrôlé par l'expéditeur (généralement le module source sur la chaîne source), les modules qui effectuent des opérations privilégiées sur les paquets IBC entrants doivent toujours vérifier que le message provient d'un canal de confiance.
Vulnérabilités
Cependant, en ce qui concerne Stride, la vérification des canaux est manquante dans le module x/autopilot. Le code suppose que les paquets ICS-20 IBC avec une adresse d'expéditeur particulière ne peuvent être envoyés que par quelqu'un qui contrôle cette adresse. Cela est vrai si nous ne considérons que les modules de transport sur des chaînes de partenaires de confiance comme EVMOS, mais les attaquants peuvent simplement envoyer des données de paquets IBC entièrement contrôlées pour utiliser des clients IBC malveillants sous leur contrôle. L'exploitation de cette vulnérabilité est relativement simple :
Créer un client IBC malveillant
Utiliser le client malveillant Craft pour créer un canal IBC vers le module de transport Stride IBC,
Et envoyez un transfert IBC malveillant en utilisant l'adresse des ClaimRecords non réclamés comme champ De. Utilisez le champ de mémo ClaimMetadata pour déclencher le pilote automatique et mettre à jour l'adresse de largage vers un compte Stride contrôlé par l'attaquant.
Volez le largage en envoyant MsgClaimFreeAmount au module x/claim
Corrections de bugs
Après avoir reçu notre rapport rapide, le contributeur Stride a rapidement retiré tous les fonds du portefeuille du revendeur Airdrop pour s'assurer qu'aucun fonds n'était en danger. Mise en œuvre d'un correctif à long terme pour s'assurer que les paquets de mise à jour de l'adresse IBC airdrop arrivent via le canal IBC de confiance approprié.
en conclusion
La prise en charge solide de la communication inter-chaînes via IBC est un avantage unique de l'écosystème Cosmos. Bien qu'IBC soit construit sur des primitives cryptographiques solides, l'intégration sécurisée avec celui-ci nécessite une bonne compréhension du modèle de confiance sous-jacent. Les développeurs qui s'appuient sur IBC et les ingénieurs en sécurité qui examinent les intégrations IBC doivent examiner attentivement la surface d'attaque exposée aux clients ou canaux IBC malveillants. Nous tenons à remercier les contributeurs de Stride pour leur traitement professionnel et leur réponse rapide à ce problème.
Lien externe WeChat
[1] Adresse du lien d'origine :
[2] x/revendication :
[3] LoadAllocationData :
[4] Enregistrement de réclamation :
[5] MsgClaimFreeAmount :
[6] x/pilote automatique :
[7] Machines solo :
Voir l'original
Le contenu est fourni à titre de référence uniquement, il ne s'agit pas d'une sollicitation ou d'une offre. Aucun conseil en investissement, fiscalité ou juridique n'est fourni. Consultez l'Avertissement pour plus de détails sur les risques.
Analyse de la vulnérabilité Stride Airdrop
Écrit par : Will
Avant-propos : Cet article décrit une vulnérabilité que Jump Crypto a découverte dans le programme de parachutage Stride : Stride est une chaîne Cosmos utilisée pour le jalonnement liquide dans l'écosystème Cosmos. Ce problème pourrait permettre à un attaquant de voler tous les parachutages non réclamés sur Stride. Au moment de la découverte, plus de 1,6 million de MSTR (équivalant à environ 4 millions de dollars) étaient à risque. Jump a signalé en privé la vulnérabilité aux contributeurs de Stride, et le problème a depuis été résolu, sans qu'aucun exploit malveillant ne se produise à la suite de nos efforts. Adresse du lien d'origine [1]
Airdrop sur la foulée
Stride effectue régulièrement de grands parachutages de son jeton natif [$STRD] pour encourager l'activité du réseau et décentraliser la gouvernance sur un large groupe de parties. Le code pour attribuer et réclamer des parachutages est à x/claim [2] implémenté dans le module. L'allocation Airdrop se fait via LoadAllocationData [3] défini par une fonction qui charge un fichier d'allocation contenant des adresses et des allocations de parachutage. Pour la plupart des parachutages, les adresses chargées décrivent les utilisateurs sur d'autres chaînes Cosmos, telles que Osmosis ou Juno, de sorte que le code les convertit d'abord en adresses Stride à l'aide de la fonction utils.ConvertAddressToStrideAddress.
Pour chaque compte dans l'airdrop, le module crée un ClaimRecord [4] , qui contient l'identifiant de largage pour un largage particulier, l'adresse convertie et le nombre de jetons alloués à l'utilisateur. Après avoir créé un ClaimRecord, un utilisateur avec l'adresse Stride correspondante peut envoyer un MsgClaimFreeAmount à la chaîne [5] Venez réclamer leur airdrop.
Cependant, cette implémentation n'a pas fonctionné lors du récent airdrop d'EVMOS car la fonction utils.ConvertAddressToStrideAddress mappe les adresses Evmos aux adresses Stride qui ne sont pas accessibles. En effet, les adresses EVMOS sont dérivées à l'aide du type de pièce 60, tandis que les adresses Stride sont dérivées à l'aide du type de pièce 118.
Afin que les utilisateurs concernés puissent toujours réclamer le largage aérien, l'équipe a ajouté la possibilité de mettre à jour l'adresse cible d'un ClaimRecord non réclamé via un message IBC inter-chaînes du compte EVMOS correspondant. Ce mécanisme de mise à jour est implémenté dans le cadre du module x/autopilot. x/pilote automatique [6] Intercepter une transmission IBC ICS-20 entrante et tenter d'extraire des instructions spécifiques à Stride de son champ mémo ou récepteur (le champ récepteur sert également de champ mémo dans les versions IBC antérieures à la v5) :
func(imIBCModule)OnRecvPacket(
ctxsdk.Context,
packetchanneltypes.Packet,
relayersdk.AccAddress,
)ibcexported.Accusé de réception{
//REMARQUE : l'accusé de réception sera écrit de manière synchrone lors de l'exécution du gestionnaire IBC.
datatransfertypes.FungibleTokenPacketData
iferr :=transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(),&data);err !=nil{
returnchanneltypes.NewErrorAcknowledgement(err)
}
[..]
// ibc-gov5 a un champ Memo qui peut stocker des informations de transfert
//Pourlesanciennesversionsd'ibc-go,lesdonnéesdoiventêtrestockéesdanslechamprécepteur
chaîne de métadonnées
ifdata.Memo !=""{//ibc-gov5+
metadata=data.Memo
}else{//beforeibc-gov5
metadata=data.Receiver
}
[..]
// analyse toutes les informations de transfert
packetForwardMetadata,err :=types.ParsePacketMetadata(métadonnées)
iferr!=néant{
returnchanneltypes.NewErrorAcknowledgement(err)
}
// Si les métadonnées analysées sont nulles, cela signifie qu'il n'y a pas de logique de transfert
//Passerlepaquetverslemiddlewaresuivant
ifpacketForwardMetadata==nil{
returnim.app.OnRecvPacket (ctx, paquet, relais)
}
//ModifierlesdonnéespaquetsenremplaçantlechampdemétadonnéesJSONparuneadressederécepteur
//pourpermettreaupaquetdecontinuerdanslapile
newData :=données
newData.Receiver=packetForwardMetadata.Receiver
bz,err:=transfertypes.ModuleCdc.MarshalJSON(&newData)
iferr!=néant{
returnchanneltypes.NewErrorAcknowledgement(err)
}
nouveaupaquet := paquet
newPacket.Data=bz
// Passez d'abord le nouveau paquet dans la pile intermédiaire
ack:=im.app.OnRecvPacket(ctx,newPacket,relayer)
if!ack.Success(){
retour
}
autopilotParams :=im.keeper.GetParams(ctx)
//Siletransfertréussit,alorsrouteverslemodulecorrespondant,siapplicable
switchroutingInfo :=packetForwardMetadata.RoutingInfo.(type){
casetypes.StakeibcPacketMetadata :
[...]
casetypes.ClaimPacketMetadata :
//Siclaimroutingestinactif(maislepaquetcontientdesinfosderoutagedanslememo)renvoieuneerreurdecaméra
[..]
im.keeper.Logger(ctx).Info(fmt.Sprintf("Forwaringpacketfrom%stoclaim",newData.Sender))
iferr :=im.keeper.TryUpdateAirdropClaim(ctx,newData,routingInfo);err !=nil{
im.keeper.Logger(ctx).Error(fmt.Sprintf("Errorupdatingairdropclaimfromautopilotfor%s:%s",newData.Sender,err.Error()))
returnchanneltypes.NewErrorAcknowledgement(err)
}
retour
défaut:
returnchanneltypes.NewErrorAcknowledgement(errorsmod.Wrapf(types.ErrUnsupportedAutopilotRoute,"%T",routingInfo))
}
}
Si les métadonnées incluses indiquent que le transfert entrant est une réclamation airdrop, le module appelle la fonction TryUpdateAirdropClaim :
func(kKeeper)TryUpdateAirdropClaim(
ctxsdk.Context,
datatransfertypes.FungibleTokenPacketData,
packetMetadatatypes.ClaimPacketMetadata,
)erreur{
[..]
//saisir les adresses pertinentes
senderStrideAddress :=utils.ConvertAddressToStrideAddress(data.Sender)
ifsenderStrideAddress==""{
returnerrorsmod.Wrapf(sdkerrors.ErrInvalidAddress,fmt.Sprintf("invalidsenderaddress(%s)",data.Sender))
}
newStrideAddress :=packetMetadata.StrideAddress
//mettre à jourl'airdrop
airdropId :=packetMetadata.AirdropId
k.Logger(ctx).Info(fmt.Sprintf("updatingairdropaddress%s(orig%s)to%sforairdrop%s",
senderStrideAddress,data.Sender,newStrideAddress,airdropId))
returnk.claimKeeper.UpdateAirdropAddress(ctx,senderStrideAddress,newStrideAddress,airdropId)
}
La fonction convertit l'adresse du paquet IBC de l'expéditeur en une adresse Stride nommée senderStrideAddress et extrait l'airdropId et la nouvelle adresse de largage newStrideAddress des métadonnées du paquet. Il appelle ensuite UpdateAirdropAddress pour mettre à jour un ClaimRecord ouvert qui correspond à la combinaison de senderStrideAddress et airdropId à la nouvelle adresse.
Avec la mise à jour de ClaimRecord, newStrideAddress peut désormais réclamer le largage. Il est important de noter que ce mécanisme de mise à jour n'est protégé que par l'adresse de l'expéditeur spécifiée dans le paquet IBC. Stride n'effectue aucune autre vérification pour s'assurer que les mises à jour des parachutages ont été déclenchées par de vrais destinataires.
Pour comprendre pourquoi il s'agit d'une grave vulnérabilité, nous devons examiner de plus près IBC, le protocole de communication inter-blockchain.
Sécurité IBC
IBC est un mécanisme de communication inter-chaînes basé sur un client léger. Semblable aux protocoles réseau classiques, le module IBC central résume de nombreux détails de bas niveau, permettant aux développeurs de créer facilement leurs propres intégrations par-dessus. La connexion d'une chaîne compatible IBC (chaîne A) à une autre chaîne compatible IBC (chaîne B) ressemble un peu à ceci :
Création d'un client de machine solo sur une chaîne activée par IBC [ID client = 06-solomachine-6]
Création du clienttendermint sur la machine solo[ClientID=07-tendermint-M48f]
ConnexioninitialiséesurchaîneactivéeIBC[ConnectionID=connection-4]
Connexioninitialiséesurmachinesolo[ConnectionID=connection-Kinb]
ConnexionconfirméesurchaîneactivéeIBC[ConnectionID=connection-4]
Connexionconfirméesurmachinesolo[ConnectionID=connection-Kinb]
InitializedchannelonIBCenabledchain[ChannelID=channel-0]
Chaîne initialisée sur une machine solo[ChannelID=channel-wwl6]
ConfirmedchannelonIBCenabledchain[ChannelID=channel-0]
Canal confirmé sur une machine solo[ChannelID=channel-wwl6]
Connection établie!
Dans un premier temps, un client léger IBC de la chaîne A est créé sur la chaîne B, et inversement.Le client IBC est identifié de manière unique par son ID client, qui est utilisé pour suivre et vérifier l'état de la chaîne distante. Une fois les clients créés, ils peuvent se connecter via une connexion, qui est initiée par une poignée de main à quatre voies. Cela crée un ConnectionEnd sur la chaîne A avec les clients légers de la chaîne B sur A, et un autre sur la chaîne B avec les clients légers de la chaîne A sur B. Les connexions, une fois créées, sont persistantes et chiffrées par deux clients légers.
La communication sur les connexions est également divisée en différents canaux. Les canaux sont identifiés par la connexion sous-jacente et les ports source et de destination. Chaque port identifie un module sur la chaîne correspondante connectée via l'IBC. Le ChannelEnd associé à la connexion est créé sur les deux chaînes et identifié par channel-id. Les données peuvent maintenant être transférées entre les deux chaînes via le canal établi.
Il est important de se rappeler que IBC est un protocole sans autorisation par défaut. Cela signifie que n'importe qui peut connecter deux chaînes compatibles IBC sans autorisation ou approbation préalable. En fait, IBC prend en charge les soi-disant Solo Machines [7] Standard, un client ne représente pas une blockchain, mais un seul hôte ou machine. Étant donné que le contenu du paquet IBC est entièrement contrôlé par l'expéditeur (généralement le module source sur la chaîne source), les modules qui effectuent des opérations privilégiées sur les paquets IBC entrants doivent toujours vérifier que le message provient d'un canal de confiance.
Vulnérabilités
Cependant, en ce qui concerne Stride, la vérification des canaux est manquante dans le module x/autopilot. Le code suppose que les paquets ICS-20 IBC avec une adresse d'expéditeur particulière ne peuvent être envoyés que par quelqu'un qui contrôle cette adresse. Cela est vrai si nous ne considérons que les modules de transport sur des chaînes de partenaires de confiance comme EVMOS, mais les attaquants peuvent simplement envoyer des données de paquets IBC entièrement contrôlées pour utiliser des clients IBC malveillants sous leur contrôle. L'exploitation de cette vulnérabilité est relativement simple :
Corrections de bugs
Après avoir reçu notre rapport rapide, le contributeur Stride a rapidement retiré tous les fonds du portefeuille du revendeur Airdrop pour s'assurer qu'aucun fonds n'était en danger. Mise en œuvre d'un correctif à long terme pour s'assurer que les paquets de mise à jour de l'adresse IBC airdrop arrivent via le canal IBC de confiance approprié.
en conclusion
La prise en charge solide de la communication inter-chaînes via IBC est un avantage unique de l'écosystème Cosmos. Bien qu'IBC soit construit sur des primitives cryptographiques solides, l'intégration sécurisée avec celui-ci nécessite une bonne compréhension du modèle de confiance sous-jacent. Les développeurs qui s'appuient sur IBC et les ingénieurs en sécurité qui examinent les intégrations IBC doivent examiner attentivement la surface d'attaque exposée aux clients ou canaux IBC malveillants. Nous tenons à remercier les contributeurs de Stride pour leur traitement professionnel et leur réponse rapide à ce problème.