> 在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.FungibleTokenPacketDataiferr:=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.Receiverbz,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.AirdropIdk.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] 獨奏機:
Stride 空投漏洞解析
撰文: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 客戶端。利用此漏洞相對簡單:
漏洞修復
在收到我們的及時報告後,Stride 貢獻者迅速從Airdrop 經銷商錢包中取出所有資金,以確保沒有資金處於風險之中。實施的長期修復確保IBC 空投地址更新數據包通過正確的可信IBC 通道到達。
結論
通過IBC 對跨鏈通信的強大支持是Cosmos 生態系統的獨特優勢。雖然IBC 建立在可靠的加密原語之上,但與其安全集成需要對底層信任模型有很好的理解。在IBC 之上構建的開發人員和審查IBC 集成的安全工程師應該仔細審查暴露給惡意IBC 客戶端或渠道的攻擊面。我們要感謝Stride 貢獻者對這個問題的專業處理和快速響應。