• YouTube Channel
  • System Status
  • VS Code Extension
  • Required tx.origin

    Overview

    What is the Required tx.origin vulnerability?

    The Ethereum Virtual Machine (EVM) recognizes the tx.origin property as the address of the account that originated the current chain of transactions, which includes inter-contract calls. Because external contract calls may execute arbitrary code, relying on tx.origin to determine contract behavior in a require() statement or similar sensitive contexts may lead to unexpected contract behavior or potential attacks on protected functionality.

    A real-world example: THORChain

    The THORChain project issued an associated token named RUNE, which was deployed as an ERC-20 token on Ethereum. Part of the functionality of the ETH_RUNE smart contract to transfer tokens will transfer to tx.origin explicitly, as seen in the code snippet below:

      function transferTo(address recipient, uint256 amount) public returns (bool) {
        _transfer(tx.origin, recipient, amount);
        return true;
      }
    
    

    (The code snippet can be found on Etherscan at line 163 of the contract, along with the implementation of the _transfer() function that uses it as an argument.)

    Because the value of tx.origin can be manipulated if transactions are intercepted by a malicious contract, the RUNE token was vulnerable to theft of users' token balances.

    Further reading: The Security Risks of THORChain

    Technical example of vulnerable code

      // SPDX-License-Identifier: Unlicense
      pragma solidity 0.8.0;
    
      contract Vulnerable {
        address owner = msg.sender;
    
        function destroyIfOwner() public {
          require(tx.origin == owner);
          destroyContract();
        }
    
        function destroyContract() private {
          selfdestruct(msg.sender);
        }
      }
    
    

    In the above example, the contract Vulnerable exposes a public function that is intended to allow only the owner of the contract to destroy it. However, because the check on the supposed owner's address is tx.origin rather than msg.sender, an attacker could potentially intercept a transaction from the actual contract owner and call the private destroyContract() function when the owner did not intend for this to happen.

    In this particular example, the problem is even worse than just the destruction of the contract, as the destroyContract() function will send any ether held by the contract to msg.sender, not the owner address, assuming that these addresses will always be the same. In the attack scenario described, however, the attacker's intercepting contract would be considered the message sender, so the attacker would both destroy the contract and also steal any ether it held.

    Technical example of how to fix the vulnerability

      // SPDX-License-Identifier: Unlicense
      pragma solidity 0.8.0;
      
      contract NotVulnerable {
          address owner = msg.sender;
    
          function destroyIfOwner() public {
          require(msg.sender == owner);
          destroyContract();
          }
        
          function destroyContract() private {
          selfdestruct(msg.sender);
          }
      }
    
    

    In the corrected example, contract NotVulnerable now checks for the value of msg.sender in the public destroyIfOwner() function. This ensures that the contract will only be destroyed if the owner of the contract calls the function directly, and not if the owner's transaction is intercepted by a malicious contract.