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!

Cross-chain re-entrancy- 1179

MateocesaroniPosted 2 Years Ago
  • Reentrancy is a fundamental attack in the Solidity security space. This is when a user can recursively call a contract while it has not had it's state fully updated. Developers should follow the Check, Effect, Interactions (CEI) pattern. However, the term cross-chain caught my eye for this post.
  • The application the author was testing was a cross-chain NFT contract. Naturally, NFTs should only exist on a single chain at once. So, verification has to be done on both chains before minting.
  • To send funds from one contract to another, a bridge is needed. Since the contract owns the asset, it can mint to create an asset or burn to remove it from existence. Then, using a proof, the other chain would know whether we owned an asset on it or not.
  • The code for the mint function did not follow the CEI pattern. The increment should occur before the call to _safeMint. This worked as follows:
    function mint(address to) public returns (uint256) {
           uint256 newWarriorId = tokenIds.current();
           _safeMint(to, newWarriorId); 
     
           tokenIds.increment();
     
           return newWarriorId;
       }
    
  • The _safeMint() function has an external function call for onERC721Received inside of it. Since the CEI pattern is not followed and the contract is not using the standard library for preventing reentrancy, there is a major problem.
  • How can this be exploited? If we simply call the _mint function again, it will fail because the token id wasn't incremented and already exists. However, there are other functions within the contract that could be interesting to us.
  • The function crossChainTransfer() is used to send the assets from one chain to another. Calling this with a particular token ID will send the token from one chain to another.
  • Since the ID is not incremented until after the mint call, we can exploit this to get two copies of it.
    1. Call mint() with the attacker contract as the recipient. NOTE: The ID hasn't been incremented.
    2. Re-enter the contract to call crossChainTransfer to transfer the new id to chain B.
    3. Call mint() again from the recipient contract to mint the same NFT once again.
  • Cross-contract reentrancy is a little bit deceiving but technically correct here. Overall, a usual bug class exploited in a unique way!