✅
Delegation
delegatecall
The goal of this level is for you to claim ownership of the instance you are given.
Things that might help:
- Look into Solidity's documentation on the
delegatecall
low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope. - Fallback methods
- Method ids
Delegatecall - Smart Contract Programmer
My comment: It is important to understand why
delegatecall
is useful. Recall that if a smart contract is deployed to the blockchain then we can't be modified its code no more. All the logic implemented in the contract will stay there forever, unless we selfdestruct the contract and deploy a new one. To get around this, we can use delegatecall
to call a "library" contract where new logic is implemented.
ethereumbook/09smart-contracts-security.asciidoc at develop · ethereumbook/ethereumbook
GitHub
DELEGATECALL - Mastering Ethereum
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Delegate {
address public owner;
constructor(address _owner) public {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
Two key points:
- The contract
Delegation
implements afallback()
function that executesdelegatecall()
. - The contract
Delegate
implements a suspicious functionpwn()
that setsmsg.sender
as the owner.
For convenience, lets define the following aliases:
- The attacker -> A
- Delegation -> B
- Delegate -> C
The idea is A calls B (delegate)calls C. Symbolically, A -> B -> C. When A -> B, we send a transaction to trigger the
fallback()
function in B. Here B initializes the delegatecall B -> C. If you watch Smart Contract Programmer's video, you should know that:- The delegatecall preserves A's context. In this case, we have
msg.sender == A
. - The delegatecall acts on B's state variable (in contrast, a normal "call" acts on C's state variable).
In C, when
pwn()
is called, the owner
state variable refers to B's owner
(instead of C's owner, since it is a delegatecall). The msg.sender
here is A. In other words, the function pwn()
sets A as B's owner, and we are done.Send a transaction to
Delegation
and calls the pwn()
function:await contract.sendTransaction({data: web3.utils.keccak256("pwn()")})
Since
pwn()
is not defined in Delegation
, the fallback()
function will be triggered. After that, Delegation
does delegatecall()
and calls the pwn()
function in Delegate
. When pwn()
is called, it updates owner
to msg.sender
, which is the attacker.Note that this
owner
refers to the owner of Delegation
since Delegation
is doing the delegatecall()
.Confirm the owner:
await contract.owner()
Click "Submit instance" and move on to the next level.
Usage of
delegatecall
is particularly risky and has been used as an attack vector on multiple historic hacks. With it, your contract is practically saying "here, -other contract- or -other library-, do whatever you want with my state". Delegates have complete access to your contract's state. The delegatecall
function is a powerful feature, but a dangerous one, and must be used with extreme care.Please refer to the The Parity Wallet Hack Explained article for an accurate explanation of how this idea was used to steal 30M USD.
Last modified 6mo ago