Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Nomad Bridge Hack- 911

samczsunPosted 3 Years Ago
  • Nomad bridge is a cross-chain protocol between Cosmos compatible blockchains, such as Moonbeam, and Ethereum. Being able to transfer ERC-20 coins between chains is amazing for scalability but is really hard to do correctly. 4 out of the 5 biggest hacks on rekt.news have been on bridges.
  • When moving stuff across chain, there appears to be two functions for this: prove() and process(). The process() function is used for uses a transaction from one chain to another after proving it.
  • To validate if the message hash is valid, the following line of code is ran:
    require(acceptableRoot(messages[_messageHash]); 
    
  • The following code can be broken into a few steps:
    1. The variable _messageHash is the keccak hash of the data being added to the blockchain.
    2. The map (dictionary in Python) is used to find the matching hash for this message. If it's valid, it will the expected root hash. Otherwise, it will return 0x0, since that's the default for a bad map find in Solidity.
    3. acceptableRoot() is called to ensure that the root hash of this message is valid. If it is, the transaction continues. Otherwise, the contract reverts.
  • So, this looks like good code, what's the problem? During an update of the smart contract, the hash 0x0 was made into a valid root! Since a Solidity map defaults to 0x0, any fake transaction will pass. Wait, how is this repeatable?
  • It turns out that the function acceptableRoot() had been updated to handle an ENUM in legacy code. Instead of having PROVEN and PROCESSED (which return true and false respectively) the legacy code checks to see if the time for the data passed in is 0. Since the structure will return a hash time that is not 0, this ALWAYS passes. Thanks to Zellic_io for tracing this down.
  • For the repeatable effect, I'm still not 100% sure about it. According to source code the 0x0 hash would be set to a value of 2 (PROCESSED) then would never be usable again. The state to update does messages[_messageHash], making it so the specific call cannot be used again. However, we can remake this call with slightly different values over and over again!
  • What is super crazy about this, is that the QuantStamp audit called this out as a potential issue. I never would have called this out, since it seemed like it was part of the design.
  • The attack lost 190 million but it appears that several of these people are whitehats who will give the money back. Regardless, I'm sure that 75% is gone forever. All of the other platforms that rely on this bridge, such as Moonbeam and EVMO, drastically sank in value from this attack.