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
.
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.
Last updated