12 - Climber


// ClimberVault.sol
function sweepFunds(address tokenAddress) external onlySweeper {
    IERC20 token = IERC20(tokenAddress);
    require(token.transfer(_sweeper, token.balanceOf(address(this))), "Transfer failed");
}

We want to drain the fund by calling sweepFunds function.

To bypass onlySweeper modifer, we will upgrade the implementation contract address to our MaliciousVault contract.

// ClimberVault.sol
function initialize(
    address admin,
    address proposer,
    address sweeper
) external initializer {
    // Initialize inheritance chain
    __Ownable_init();
    __UUPSUpgradeable_init();

    // Deploy timelock and transfer ownership to it
    transferOwnership(address(new ClimberTimelock(admin, proposer)));

    _setSweeper(sweeper);
    _setLastWithdrawal(block.timestamp);
    _lastWithdrawalTimestamp = block.timestamp;
}

The initial owner right after the deployment is an instance of ClimberTimelock contract.

From ClimberTimelock we can make external calls by calling execute function.

But because execute function does not keep C-E-I pattern properly, (because it checks if the executing operation is schedule after the execution) we can first do whatever we want and schedule our operations right before it gets to the last require statement.

To schedule our operations, we will grant PROPOSER_ROLE to our attacker acount.

We can do this simply by calling grantRole function, since the contract itself has ADMIN_ROLE.

And to execute multiple operations, we will first update the delay to 0.

Finally, we will upgrade the implementation contract to our malicious contract that implements sweepFunds function without any restriction.

  1. ClimberTimelock -> make the delay 0 so we can execute multiple operations

  2. ClimberTimelock -> grant PROPOSER_ROLE to our attacker account (so we can

  3. ClimberVault -> upgrade Implementation address to our MaliciousVault contract

  4. ClimberTimelock -> schedule these operations so we can pass the last require condition in execute

  5. ClimberVault -> delegatecall sweepFunds function in MaliciousVault (Now all the tokens are moved to AttackClimber from ClimberVault)

  6. AttackClimber -> move all funds to attacker account

Solution

Done! 😎

Last updated