29080 - [SC - Insight] Uninitialized uups upgradeable can lead to loss...

Submitted on Mar 6th 2024 at 19:08:33 UTC by @SAAJ for Boost | Puffer Finance

Report ID: #29080

Report type: Smart Contract

Report severity: Insight

Target: https://etherscan.io/address/0x7276925e42f9c4054afa2fad80fa79520c453d6a

Impacts:

  • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Vulnerability Details

PufferDepositor and PufferVault contracts inherit UUPSUpgradeable contract by OZ. These contracts are deployed using a proxy pattern whereby the implementation contract is used by the proxy contract for all its logic. This helps to facilitate future upgrades by pointing the proxy contract to a new and upgraded implementation contract.

However, when the implementation contract is left uninitialized, it is possible for any attacker to gain ownership of the restricted role in the implementation contract for both, refer to this article. This is clearly present due to uninitialized UUPSUpgradeable contract in PufferDepositor and PufferVault.

OpenZeppelin’s clearly provide guide in context of initializing OZ upgradeable contracts in the initialize function.

Impact

The problem is that that implementation vault and depositor contracts are not initialized, which means that anybody can initialize the contract to become the owner.

The POC clearly shows how the attack_Contract contract inherits both PufferDepositor and UUPSUpgradeable. The constructor initializes both contracts.

The malicious contract contain logic for both selfdestruct and upgrade to a new implementation.

A selfDestruct function is included for demonstration purposes. This function allows the contract to be destroyed and its funds sent to a specified address.

The malicious_AuthorizeUpgrade function will be used to call _authorizeUpgrade with a new implementation address afterwards the selfDestruct is called which will destroy the malicious attack_Contract with leaving no way to trace back the new implementation contract.

Once the attacker has ownership they are able to perform an upgrade of the implementation contract's logic contract and delegate call into any arbitrary contract, allowing them to transfer all asset to themselves leading to direct loss of funds.

The attacker also can then destroy the contract by doing a delegate call (via the execute function) to a function with the self-destruct opcode. Once the implementation is destroyed vault will be unusable.

since there's no logic in the proxies to update the implementation - that means this is permanent (i.e. there's no way to call any function on vault anymore, it will be simply dead).

Code Reference

https://github.com/PufferFinance/pufETH/blob/2768d69196717e9f77a6837153b426e06e15c51f/src/PufferDepositor.sol#L47

https://github.com/PufferFinance/pufETH/blob/2768d69196717e9f77a6837153b426e06e15c51f/src/PufferVault.sol#294

Recommendations

The recommendation is made for initializing the UUPSUpgradeable contract in implementation contract of PufferDepositor and PufferVault.

// only needed for UUPS
 __UUPSUpgradeable_init();

Proof of concept

POC

Here is a POC clearly evident absence of initialization of UUPSUpgradeable contract for both PufferDepositor and PufferVault that can be used by an attacker to upgrade and then destroy the implementation contract.


// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;


import "openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol";
import "../src/PufferDepositor.sol";

contract attack_Contract is PufferDepositor, UUPSUpgradeable {
    constructor() PufferDepositor() UUPSUpgradeable()

    function initialize(address accessManager) public initializer {
        __AccessManaged_init(accessManager);
        __UUPSUpgradeable_init();
    }

    function selfDestruct() public restricted {
        selfdestruct(payable(attacker));
    }

    function test_maliciousAuthorizeUpgrade() public restricted {

        address newImplementation = address(malicious_Contract);
        _authorizeUpgrade(newImplementation);
    }
}

Last updated