Telephone | Ethernaut Level 4 Walk-Through

Telephone asks us to claim ownership of the contract. Learn the difference between tx.origin and msg.sender.

Telephone Homepage

It supplies us with the following source code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Telephone {

  address public owner;

  constructor() {
    owner = msg.sender;
  }

  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }
}

The contract seems simple enough, with just one function changeOwner we can see the requirements for claiming ownership:

if (tx.origin != msg.sender) {
    owner = _owner;
}

It seems that tx.origin can not (!=) equal the msg.sender.

So let’s head to the documentation to define these two values and figure out the difference.

tx.origin vs. msg.sender

tx.origin (address): sender of the transaction (full call chain) Solidity Docs
msg.sender (address): sender of the message (current call) Solidity Docs

Both refer to an address that is attached to the transaction, the difference seems to lie in which call it comes from.
The reason this can be a security risk is tx.origin will always be the address that the original call chain started from.

Meaning a bad actor can trick you into calling a malicious contract, forwarding the call with tx.origin (your wallet address).
There is a great example of how this works in the Solidity Docs security section, tx.origin.

If your wallet had checked msg.sender for authorization, it would get the address of the attack wallet, instead of the owner address. But by checking tx.origin, it gets the original address that kicked off the transaction, which is still the owner address. The attack wallet instantly drains all your funds.

tx.origin docs

We found what we need to exploit to take control of this contract.
Let’s do some more digging on tx.origin and remediating the problem.

A quick Google search finds a few pages about best practices, all of which saying not to use tx.origin for authorization.

Never use tx.origin for authorization, another contract can have a method which will call your contract (where the user has some funds for instance) and your contract will authorize that transaction as your address is in tx.origin.

tx.origin – Consensys Ethereum Smart Contract Best Practices

msg.sender is the preferred way to authorize because it comes from the address making the current call.

tx.origin is a global variable in Solidity which returns the address of the account that sent the transaction. Using the variable for authorization could make a contract vulnerable if an authorized account calls into a malicious contract. A call could be made to the vulnerable contract that passes the authorization check since tx.origin returns the original sender of the transaction which in this case is the authorized account.

Authorization through tx.origin – SWC Registry

Solution

Get a new instance of the level and let’s get started.

We know that our tx.origin is going to be our address, we need a second call from an external contract that will make the msg.sender a different address.

So let’s head to Remix IDE and develop our malicious contract.

Telephone contract

First I copied and pasted our given contract into a new file telephone.sol and entered my instance address into the ‘At Address’ box.

Now create a new file for our contract.

Attack contract

In our attacking contract we need a function that will call changeOwner from the original contract, passing in tx.origin , then we should have claimed ownership over the contract.

We need to reference the Telephone contract with our instance address in order to call it.

teleattack.sol contract

Once we have configured the contract it’s time to deploy.

Scroll down and expand both contracts to see the possible transactions.

Deployed contracts screen

Let’s pre-check who the owner of the challenge contract is using the owner button:

Results of owner call

Okay, the owner is not me, let’s see if we can claim it using our malicious contract.
Make a call to teleAttack.
Note: I’m unsure that the transaction needs to be payable since I sent 0 value, but in testing I was using it payable on a VM and left it as is before deploying to testnet. It 100% needs to be public though.

teleAttack function call

Once the transaction has been mined we can go back to the challenge contract and call owner again.

telephone.owner()

We have become the owner of the contract, its time to submit our instance.

Level 4 complete

Congratulations, we have finished another level together!

DAVE

Leave a Comment

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

Scroll to Top