Ethernaut Level 04 - Telephone

Ethernaut Level 04 - Telephone

Analysis and solution for Ethernaut's level 04 - Telephone, with Solidity and Foundry


This level requires us to become the owner of the contract. Plain and simple. Or is it?

To exploit this, you have to first know the difference between tx.origin and msg.sender. Let's dive in.

tx.origin vs msg.sender

Let me simplify this using an infographic :


  1. If I'm making a call to a contract A, the contract will see both the tx.origin and the msg.sender as my accounts/contract's address with which I made the call.
  2. If I'm making a call to a contract A and then the contract A is making another call to contract B, then contract B will see my account's address in the tx.origin but the msg.sender will be contract A's address.

This means that the value of tx.origin does not change and is constant. It takes the address of the user or contract who initiates the transaction whereas msg.seder is the address of the intermediate contract or user that called the function.


Now that the distinction is made, let's look into the contract logic.

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

The function changeOwner() has public visibility which means that it can be called by anyone.

It has a validation condition that checks if the tx.origin is not equal to msg.sender. If this is true, then sets the new owner to the address passed in the function arguments.

So to exploit this level, we just need to make sure that our tx.origin and msg.sender do not match when the Ethernaut's instance receives the function call to changeOwner().

To bypass this validation, as explained above, we can make use of an intermediary contract A, to make the changeOwner() call to contract B (Ethernaut's contract).

The Exploit

We won't be using foundry scripts since it will be simpler and easier to just deploy a contract on the Rinkeby network and make a call to the deployed contract.

The PoC code is inside src/level04. Here's how it looks:

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

import "../instances/Ilevel04.sol";

contract Tele {
    Telephone level4 = Telephone(0xf2585aB001D77CEF708d2E435D60F9Dbd4fC4aB4);

    function exploit() external {

This contract just makes a call to the changeOwner() function of Ethernaut's instance with our wallet's address as the parameter so we can become the new owner.

We will deploy the contract using the following command:

forge create Tele --private-key $PKEY --rpc-url $RPC_URL

To make a call to our exploit function:

cast send 0x3C4AaE7bce13f0fdE603F45Cff878F714359fEE2 "exploit()" --private-key $PKEY --rpc-url $RPC_URL


Once this is done, we will become the new owner by bypassing the tx.origin != msg.sender validation. The instance can now be submitted to finish the level.

My Github Repository containing all the codes:


Whenever you think of making use of tx.origin in a contract, make sure it can't be abused in phishing attacks like the one mentioned on Solidity By Example.