Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: high
Invalid

Incorrect Initialization Order

Summary

The base/Vault.sol contract within the stake.link ecosystem exhibits a critical vulnerability related to its initialization process. Specifically, the contract disables initializers in its constructor using _disableInitializers(), necessitating the subsequent invocation of the initialize function to set up essential parameters such as ownership. However, the initialize function lacks adequate protection, allowing it to be called multiple times by any address. This oversight can be exploited by malicious actors to reinitialize the contract, potentially seizing ownership and gaining full control over the contract's functionalities and funds.

Vulnerability Details

Unprotected initialize Function

function initialize(address _owner) public {
__Ownable_init();
transferOwnership(_owner);
}

Explanation:
The initialize function in base/Vault.sol is intended to set up the contract's ownership. However, it lacks the initializer modifier from OpenZeppelin's upgradeable contracts. Without this modifier, the function can be called multiple times by any address, allowing unauthorized reinitialization of the contract.

Attacker Contract Exploiting the Vulnerability

contract Attacker {
Vault public vulnerableVault;
constructor(address _vaultAddress) {
vulnerableVault = Vault(_vaultAddress);
}
function exploit(address newOwner) public {
vulnerableVault.initialize(newOwner);
}
}

Explanation:
The Attacker contract is designed to interact with the vulnerable Vault contract. By invoking the unprotected initialize function, the attacker can set themselves as the new owner of the contract. This grants them exclusive access to owner-restricted functions, effectively taking control of the contract.

Simulating the Exploit

vm.startPrank(attackerAddress);
attacker.exploit(attackerAddress);
vm.stopPrank();

Explanation:
Using Foundry's cheatcodes, the simulation starts by impersonating the attackerAddress. The attacker then calls the exploit function, reinitializing the Vault contract and setting themselves as the new owner. This change is verified by asserting the new ownership.

Verifying Ownership Takeover

assertEq(vault.owner(), attackerAddress, "Owner should be attackerAddress");

Explanation:
After the exploit, this assertion confirms that the attackerAddress has successfully taken ownership of the Vault contract. With ownership established, the attacker gains the ability to execute any owner-restricted functions, leading to potential fund mismanagement and contract manipulation.

Impact

The inability to properly restrict the initialize function poses several severe risks:

  • Ownership Takeover: An attacker can become the owner, granting them full control over the contract.

  • Funds Drain: With ownership, the attacker can withdraw all staked tokens, manipulate staking strategies, or redirect rewards.

  • Platform Disruption: The attacker can pause the contract, alter fee structures, or introduce malicious strategies, undermining the platform's functionality and trustworthiness.

  • Erosion of User Trust: Such vulnerabilities can lead to significant financial losses for users and damage the platform's reputation within the community.

Tools Used

  • Manual Review

  • Foundry

Recommendations

To mitigate the identified vulnerability and enhance the security of the base/Vault.sol contract, the following measures are recommended:

1. Implement the initializer Modifier

Ensure that the initialize function can only be called once by using OpenZeppelin's initializer modifier. This prevents multiple invocations and unauthorized reinitializations.

function initialize(address _owner) public initializer {
__Ownable_init();
transferOwnership(_owner);
}

2. Restrict Access to the initialize Function

Add access control to the initialize function to ensure that only the deployer or a designated admin can call it during the contract's deployment phase.

function initialize(address _owner) public initializer onlyOwner {
__Ownable_init();
transferOwnership(_owner);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.