In level 4 of the Ethernaut Game our goal is to claim ownership of the Telephone
contract.
// 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;
}
}
}
This is a simple contract with only one function and constructor that assigns the ownership to the address that deployed the contract.
Considering that we have only one function it is fairly easy to guess that need to focus on it if we want to win the game.
The changeOwner
function takes as argument an address and verifies if (tx.origin != msg.sender)
.
To claim the ownership we need to first understand what is the difference between tx.origin
and msg.sender
and why contracts that use the tx.origin
to authorize users are vulnerable to phishing attacks.
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
For more details, I encourage you to read my post about tx.origin
Hack Solidity: Tx Origin Attacks
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.
Go to Remix IDE and paste a Telephone
smart contract. Next, a open new file and create the following contract. Do not forget to import the Telephone
contract.
Here we have a constructor that takes the Telephone's contract address. Next, we have a malicious function changeOwner
that takes the address of the attacker and uses it to change the ownership of the Telephone
contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "./Telephone.sol";
contract Attack {
Telephone telephone;
constructor(address _address) public {
telephone = Telephone(_address);
}
function changeOwner(address _address) public {
telephone.changeOwner(_address);
}
}
Before we deploy the contract let's first check the current Telephone's owner adress.
Before you deploy the contract in Remix you need to change the environment to Injected Web3. Next, in the Deploy field put a Telephone
contract address
To check the address run the following command in the browser console.
Once you deploy the contract in the next step you can call the changeOwner
function. As an argument put your MetaMask address.
When you call the function you should claim the ownership of the Telephone smart contract. To check that run the following command in the browser console.
As you can see the owner's address has changed, i.e. I claimed the ownership. Note, your wallet address is different, thus your output will differ from mine.
Top comments (0)