Stride 空投漏洞解析

在IBC 之上構建的開發人員和審查IBC 集成的安全工程師應該仔細審查暴露給惡意IBC 客戶端或渠道的攻擊面。

撰文:Will

**前言:**本文描述了Jump Crypto 在Stride 空投程序中發現的一個漏洞:Stride 是一個Cosmos 鏈,用於在Cosmos 生態系統中進行流動性質押。這個問題可能會讓攻擊者竊取Stride 上所有無人認領的空投。在發現時,超過160 萬STRD(相當於大約400 萬美元)處於危險之中。 Jump 私下向Stride 貢獻者報告了該漏洞,該問題現已修復,通過努力沒有發生惡意利用的事件。原文鏈接地址 [1]

Stride 上的空投

Stride 定期對其原生[$STRD]代幣進行大量空投,以激勵網絡活動並在廣泛的各方群體中分散治理。分配和領取空投的代碼在x/claim [2] 模塊中實現。空投分配是通過LoadAllocationData [3] 函數定義的,該函數加載一個包含地址和空投分配的分配文件。對於大多數空投,加載的地址描述了其他Cosmos 鏈上的用戶,例如Osmosis 或Juno,因此代碼首先使用utils.ConvertAddressToStrideAddress 函數將它們轉換為Stride 地址。

對於空投中的每個帳戶,該模塊都會創建一個ClaimRecord [4] ,其中包含特定空投的空投標識符、轉換後的地址以及分配給用戶的代幣數量。創建ClaimRecord 後,具有相應Stride 地址的用戶可以通過向鏈發送MsgClaimFreeAmount [5] 來領取他們的空投。

但是,此實現在最近的EVMOS 空投期間不起作用,因為utils.ConvertAddressToStrideAddress 函數將Evmos 地址映射到不可訪問的Stride 地址。這是因為EVMOS 地址是使用硬幣類型60 派生的,而Stride 地址是使用硬幣類型118 派生的。

為了讓受影響的用戶仍然可以領取空投,該團隊添加了通過跨鏈IBC 更新無人認領的ClaimRecord 的目標地址的功能來自相應EVMOS 帳戶的消息。此更新機製作為x/autopilot 模塊的一部分實現。 x/autopilot [6] 攔截傳入的IBC ICS-20 傳輸並嘗試從其備忘錄或接收方字段中提取特定於Stride 的指令(接收方字段在v5 之前的IBC 版本中兼作備忘錄字段):

func(imIBCModule)OnRecvPacket(

ctxsdk.上下文,

packetchanneltypes.Packet,

relayersdk.AccAddress,

)ibcexported.Acknowledgement{

//注意:確認將在 IB 處理過程中同步寫入。

datatransfertypes.FungibleTokenPacketData

iferr:=transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(),&data);err!=nil{

returnchanneltypes.NewErrorAcknowledgement(錯誤)

}

[..]

//ibc-gov5hasaMemofieldthatcanstoreforwardinginfo

//對於舊版本的ibc-go,數據必須存儲在receiverfield

元數據字符串

ifdata.Memo!=""{//ibc-gov5+

元數據=數據.備忘錄

}else{//beforeibc-gov5

元數據=data.Receiver

}

[..]

//解析任何轉發信息

packetForwardMetadata,錯誤:=類型。ParsePacketMetadata(元數據)

iferr!=無{

returnchanneltypes.NewErrorAcknowledgement(錯誤)

}

//如果parsed metadata為nil,說明沒有轉發邏輯

//將數據包向下傳遞到下一個中間件

ifpacketForwardMetadata==nil{

returnim.app.OnRecvPacket(ctx,packet,relayer)

}

//通過將 JSON 元數據字段替換為接收地址來修改數據包數據

//toallowthepackettocontinuedownthestack

新數據:=數據

newData.Receiver=packetForwardMetadata.Receiver

bz,err:=transfertypes.ModuleCdc.MarshalJSON(&newData)

iferr!=無{

returnchanneltypes.NewErrorAcknowledgement(錯誤)

}

新數據包:=數據包

newPacket.Data=bz

//首先將新數據包傳遞到中間件堆棧

ack:=im.app.OnRecvPacket(ctx,newPacket,relayer)

如果!ack.成功(){

返回

}

自動駕駛參數:=im.keeper.GetParams(ctx)

//如果傳輸成功,則路由到相應的模塊,如果適用

switchroutingInfo:=packetForwardMetadata.RoutingInfo.(類型){

casetypes.StakeibcPacketMetadata:

[...]

casetypes.ClaimPacketMetadata:

//Ifclaimsroutingisinactive(butthepackethadroutinginfointhememo)返回一個ackerror

[..]

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(錯誤)

}

返回

默認:

returnchanneltypes.NewErrorAcknowledgement(errorsmod.Wrapf(types.ErrUnsupportedAutopilotRoute,"%T",routingInfo))

}

}

如果包含的元數據表明傳入傳輸是空投聲明,則模塊調用TryUpdateAirdropClaim 函數:

func(kKeeper)TryUpdateAirdropClaim(

ctxsdk.上下文,

datatransfertypes.FungibleTokenPacketData,

packetMetadatatypes.ClaimPacketMetadata,

)錯誤{

[..]

//抓取相關地址

senderStrideAddress:=utils.ConvertAddressToStrideAddress(數據.發件人)

ifsenderStrideAddress==””{

returnerrorsmod.Wrapf(sdkerrors.ErrInvalidAddress,fmt.Sprintf("invalidsenderaddress(%s)",data.Sender))

}

newStrideAddress:=packetMetadata.StrideAddress

//更新空投

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)

}

函數轉換發送方IBC 數據包的地址到名為senderStrideAddress 的Stride 地址,並從數據包元數據中提取airdropId 和新的空投地址newStrideAddress。然後它調用UpdateAirdropAddress 來更新一個打開的ClaimRecord,該記錄與senderStrideAddress 和airdropId 的組合匹配到新地址。

隨著ClaimRecord 的更新,newStrideAddress 現在可以領取空投了。需要注意的重要一點是,此更新機制僅受IBC 數據包內指定的發件人地址的保護。 Stride 不執行任何其他驗證來確保空投的更新是由真正的接收者觸發的。

要了解為什麼這是一個嚴重的漏洞,我們需要仔細研究IBC,即區塊鏈間通信協議。

IBC 安全

IBC 是一種基於輕客戶端的跨鏈通信機制。與經典網絡協議類似,核心IBC 模塊抽象了許多底層細節,使開發人員可以輕鬆地在其上構建自己的集成。將一個支持IBC 的鏈(鏈A)連接到另一個支持IBC 的鏈(鏈B)看起來有點像這樣:

CreatedsolomachineclientonIBCenabledchain[ClientID=06-solomachine-6]

在單機上創建了 tendermint 客戶端 [ClientID=07-tendermint-M48f]

啟用IBC的鏈上的初始化連接[ConnectionID = connection-4]

在單機上初始化連接 [ConnectionID = connection-Kinb]

在啟用 IBC 的鏈上確認連接 [ConnectionID=connection-4]

Confirmedconnectiononsolomachine[ConnectionID=connection-Kinb]

在 IBCenabledchain 上初始化的通道 [ChannelID=channel-0]

已初始化頻道在單機[ChannelID=channel-wwl6]

已確認通道啟用 IBC 鏈 [ChannelID = channel-0]

確認頻道onsolomachine[ChannelID=channel-wwl6]

連接已建立!

第一步,在鏈B 上創建鏈A 的IBC 輕客戶端,反之亦然. IBC 客戶端由其客戶端ID 唯一標識,用於跟踪和驗證遠程鏈的狀態。創建客戶端后,它們可以通過一個連接來連接,該連接是通過四次握手啟動的。這在鏈A 上創建了一個ConnectionEnd,鏈B 的輕客戶端在A 上,另一個在鏈B 上,鏈A 的輕客戶端在B 上。連接一旦創建就會持久,並受到兩個輕客戶端的加密保護。

通過連接進行的通信還分為不同的通道。通道由底層連接以及源端口和目標端口標識。每個端口標識通過IBC 連接的相應鏈上的一個模塊。與Connection 關聯的ChannelEnd 在兩個鏈上創建並通過channel-id 標識。現在可以通過已建立的通道在兩個鏈之間傳輸數據。

重要的是要記住,默認情況下IBC 是一種無需許可的協議。這意味著任何人都可以連接任何兩條支持IBC 的鏈,而無需事先授權或批准。實際上,IBC 支持所謂的Solo Machines [7] 標準,客戶端不代表區塊鏈,而是代表單個主機或機器。由於IBC 數據包內容完全由發送方(通常是源鏈上的源模塊)控制,因此根據傳入的IBC 數據包執行特權操作的模塊始終需要驗證消息是否來自可信通道。

漏洞

然而,就Stride 而言x/autopilot 模塊中缺少通道檢查。該代碼假定具有特定發件人地址的ICS-20 IBC 數據包只能由對該地址有控制權的人發送。如果我們只考慮EVMOS 等可信賴合作夥伴鏈上的傳輸模塊,這是正確的,但攻擊者可以簡單地發送完全受控的IBC 數據包數據,以使用他們控制下的惡意IBC 客戶端。利用此漏洞相對簡單:

  1. 創建惡意IBC 客戶端
  2. 使用惡意客戶端Craft 創建到Stride IBC 傳輸模塊的IBC 通道,
  3. 並使用無人認領的ClaimRecords 的地址作為發件人字段發送惡意IBC 傳輸。使用ClaimMetadata 備忘錄字段觸發自動駕駛並將空投地址更新為攻擊者控制的Stride 帳戶。
  4. 通過向x/claim 模塊發送MsgClaimFreeAmount 來竊取空投

漏洞修復

在收到我們的及時報告後,Stride 貢獻者迅速從Airdrop 經銷商錢包中取出所有資金,以確保沒有資金處於風險之中。實施的長期修復確保IBC 空投地址更新數據包通過正確的可信IBC 通道到達。

結論

通過IBC 對跨鏈通信的強大支持是Cosmos 生態系統的獨特優勢。雖然IBC 建立在可靠的加密原語之上,但與其安全集成需要對底層信任模型有很好的理解。在IBC 之上構建的開發人員和審查IBC 集成的安全工程師應該仔細審查暴露給惡意IBC 客戶端或渠道的攻擊面。我們要感謝Stride 貢獻者對這個問題的專業處理和快速響應。

微信外鏈

[1] 原文鏈接地址:

[2] x/聲明:

[3] 加載分配數據:

[4] 理賠記錄:

[5] MsgClaimFreeAmount:

[6] x/自動駕駛儀:

[7] 獨奏機:

本頁面內容僅供參考,非招攬或要約,也不提供投資、稅務或法律諮詢。詳見聲明了解更多風險披露。
  • 讚賞
  • 留言
  • 分享
留言
0/400
暫無留言
交易,隨時隨地
qrCode
掃碼下載 Gate APP
社群列表
繁體中文
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)