Appearance
Climber: Scaling the Heights of Smart Contract Security for a 10 Million DVT Heist
The world of decentralized finance thrives on innovation, but with great power comes the need for ironclad security. Enter "Climber," a thrilling CTF challenge from Damn Vulnerable DeFi, where a hefty 10 million DVT tokens are locked away in a seemingly impenetrable vault. Our mission? To rescue these tokens and deposit them into a designated recovery account. This isn't just a simple snatch-and-grab; it's a multi-stage operation requiring a deep understanding of upgradeable contracts, access control, and a cunning exploitation of a critical timing vulnerability.
The Fortress: A Vault Built for Security (or so it seemed)
At the heart of the challenge lies the ClimberVault, safeguarding those precious DVT tokens. It boasts several layers of defense:
- Upgradeability (UUPS): The vault follows the UUPS pattern, meaning its logic can be updated, but only its legitimate owner can authorize such an upgrade.
- Timelock Ownership: Crucially, the vault isn't owned by a regular EOA (Externally Owned Account) but by a
ClimberTimelockcontract. This means any action requiringonlyOwnerpermissions (like upgrading the vault or withdrawing funds) must first pass through the timelock's carefully controlled process. - Limited Withdrawals: The timelock can only withdraw a small
WITHDRAWAL_LIMIT(1 DVT) everyWAITING_PERIOD(15 days), preventing large, rapid withdrawals. - Emergency Sweeper: An
_sweeperrole exists, capable of sweeping all tokens in an emergency. However, this role is tightly controlled.
The ClimberTimelock itself adds another layer of security:
- Role-Based Access Control: Actions like
schedule(which queues up operations) can only be performed by accounts holding thePROPOSER_ROLE. - Time Delay: Once an action is scheduled by a Proposer, it must wait a
delayperiod (initially 1 hour) before it can beexecuted. - Self-Administered Delay: The timelock's
updateDelayfunction can only be called by the timelock contract itself, seemingly preventing external manipulation of the delay.
This setup paints a picture of robust security. A malicious actor would need to bypass a timelock, acquire the Proposer role, wait for a delay, and then somehow wrest control of the vault or the sweeper role. Sounds difficult, right?
The Ascent: Uncovering the Critical Flaw
The vulnerability in "Climber" hinges on a subtle but devastating flaw in the ClimberTimelock::execute function's logic. While it performs a state check at the end of the function (if (getOperationState(id) != OperationState.ReadyForExecution) revert NotReadyForExecution(id);), the actual calls to the target contracts (targets[i].functionCallWithValue(...)) are performed before this check.
This "execute-before-check" vulnerability allows for an ingenious timing attack, escalating privileges in a single, atomic transaction.
Here's how the exploit unfolds, using a malicious MyContract as our staging ground:
The Trojan Horse Execution: The
playerinvokesMyContract::play(), which in turn calls_climberTimelock.executewith a carefully crafted list oftargetsanddataElements. These targets include theClimberTimelockitself andMyContract.Atomic Privilege Escalation: Inside the
_climberTimelock.executefunction's loop:- Delay Annihilation: The first target is the
_climberTimelockitself, callingupdateDelay(0). Because themsg.senderfor this internal call is the timelock contract itself, this succeeds, instantly reducing the timelock's delay to zero. - Proposer Role Granted: The second target is also the
_climberTimelock, callinggrantRole(PROPOSER_ROLE, MY). Again, as the call originates from the timelock itself (which holds theADMIN_ROLE),MyContractis now granted thePROPOSER_ROLE! - Self-Scheduling Backdoor: The third target is
MyContractitself. The timelock callsMyContract'sfallback()function. InsideMyContract::fallback(), it uses its newly acquiredPROPOSER_ROLEand the now-zerodelayto schedule the exact same sequence of operations (updateDelay(0)andgrantRole(PROPOSER_ROLE, MY)). This scheduling completes immediately because the delay is zero.
- Delay Annihilation: The first target is the
Bypassing the State Check: After the loop completes, the
_climberTimelock.executefunction performs itsgetOperationStatecheck. Because the operations were just scheduled byMyContract'sfallback()(and the delay is zero), the operation is now indeedReadyForExecution. Theexecutefunction successfully completes without reverting!
At this point, MyContract has the PROPOSER_ROLE and the timelock's delay is zero.
The Final Strike: Upgrading the Vault and Sweeping Funds
With full control over the timelock, the rest of the heist is straightforward:
- Vault Upgrade:
MyContractcallsplay2(), which schedules and immediately executes anupgradeToAndCalloperation on theClimberVault. The vault is upgraded to a maliciousYourContractimplementation. - Backdoor Sweeper:
YourContractcontains a critical backdoor: itssetSweeperfunction lacks any access control. It allows anyone to set a new sweeper address and immediately sweep all tokens to that address. - Tokens Rescued:
MyContractcalls the newly deployedYourContract(via the vault's address) to executesetSweeper(player, token). This sets the player as the new sweeper and instantly transfers all 10 million DVT tokens to theplayer. - Recovery: Finally, the
playertransfers the 10 million DVT from their account to the designatedrecoveryaccount, completing the challenge.
Lessons from the Climb
The Climber challenge is a masterful demonstration of how seemingly secure design patterns can be undermined by subtle implementation flaws. Key takeaways include:
- Order of Operations Matters: Executing actions before validating the state can lead to devastating vulnerabilities, especially in complex multi-call scenarios.
- Self-Administration Risks: While allowing a contract to manage itself (e.g., granting itself roles, updating its own parameters) can seem elegant, it introduces a powerful vector for privilege escalation if its internal logic can be manipulated.
- Upgradeability is a Double-Edged Sword: UUPS proxies offer flexibility, but if the control over upgrades (the
onlyOwnerin_authorizeUpgrade) is compromised, the entire system can be replaced with malicious code. - Thorough Audit of Complex Interactions: Systems involving multiple interconnected contracts, especially those with timelocks and upgrade mechanisms, require meticulous auditing of how they interact and how state changes are handled across function calls.
"Climber" serves as a powerful reminder that true security lies not just in the patterns we choose, but in the precise, line-by-line implementation of their logic. A single misplaced check can turn a fortress into a stepping stone for an attacker.