Asymmetry contest - giovannidisiena's results

A protocol to help diversify and decentralize liquid staking derivatives.

General Information

Platform: Code4rena

Start Date: 24/03/2023

Pot Size: $49,200 USDC

Total HM: 20

Participants: 246

Period: 6 days

Judge: Picodes

Total Solo HM: 1

Id: 226

League: ETH

Asymmetry Finance

Findings Distribution

Researcher Performance

Rank: 63/246

Findings: 2

Award: $84.81

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

3.4908 USDC - $3.49

Labels

bug
3 (High Risk)
satisfactory
duplicate-1098

External Links

Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L78-L81 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L98-L99

Vulnerability details

Impact

Due to the way in which the SafEth share price is calculated, an attacker can front-run the first depositor's transaction and steal funds through an inflation attack. SafEth::stake calculates the share price by dividing the total asset amount by the total supply of shares, which is 0 at the time of the first deposit and means that the first depositor will receive 1 share for every wei deposited. The exploit is performed by front-running a large initial deposit and inflating the balance of the staked ether derivative contracts by sending a large number of tokens directly. This causes the preDepositPrice to become very large which can result in the victim receiving little/no shares due to rounding given there is no check that the amount to mint is not zero. The result is that the attacker profits at the expense of the victim.

Proof of Concept

The preDepositPrice which is used in calculations of the amount of shares to mint in exchange for a given amount of ether is calculated by:

if (totalSupply == 0) preDepositPrice = 10 ** 18; // initializes with a price of 1 else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;

Example exploit steps:

  1. Attacker calls SafEth with 1 wei for 1 share.
  2. Attacker deposits 100 ether worth of staked ether derivative tokens to each derivative token contract in the protocol assuming each has a weight of 1/3.
  3. Victim deposits 200 ether for 0 shares due to rounding, assuming a preDepositPrice of 300e18 given totalSupply of 1e18 and underlyingValue of 300e18. Thus, the mintAmount calculation is 200e18/300e18 which floor rounds to 0.
  4. Attacker withdraws 1 share, profiting 200 ether.

Severity Justification

The first depositor may not receive shares in exchange for their assets if their transaction is front-run and the total asset amount has been manipulated, so this is evaluated to be HIGH.

Tools Used

Manual review

As stated in other similar reports, one solution to this problem is to burn the first 1000 shares thereby increasing the cost to perform this attack by the same factor. Additionally, ensure the number of shares is non-zero to prevent an attacker from stealing all the funds in the case where subsequent deposits are less than SafEth::underlyingValue:

require(mintAmount != 0, "No shares minted");

#0 - c4-pre-sort

2023-04-04T12:42:00Z

0xSorryNotSorry marked the issue as duplicate of #715

#1 - c4-judge

2023-04-21T14:54:35Z

Picodes marked the issue as satisfactory

Awards

81.3214 USDC - $81.32

Labels

bug
3 (High Risk)
satisfactory
duplicate-1004

External Links

Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L73

Vulnerability details

Impact

The Asymmetry SafEth protocol aims to help diversify and decentralize liquid staking derivatives, exchanging ether staked in the protocol for staked ether derivative tokens based on some relative weighting. A function exposed by these derivative wrapper contracts, IDerivative::ethPerDerivative, takes uint256 _amount as argument which is unused in all invocations except for the case when the derivative contract is Reth. This is because the rETH deposit pool has a limit above which the balance is capped, currently set to 5000 ether as given by RocketDAOProtocolSettingsDeposit::getMaximumDepositPoolSize, so any additional rETH is made up by the SafEth protocol through swaps on Uniswap V3.

Due to a bug in SafEth::stake which passes derivatives[i].balance() as _amount to IDerivative::ethPerDerivative, after a sufficient number of deposits to Reth, this derivative contract balance of rETH could exceed the remaining room in the pool, causing ethPerDerivative to erronously quote the UniswapV3 pool spot price as defined by Reth::poolPrice instead of RocketTokenRETHInterface::getEthValue. Also note that whilst this function expects an ether balance, it is in fact receiving an rETH balance (the same is true for the other derivative tokens; however, as stated above, this value is not used). Given these preconditions, an attacker could use a flash loan to manipulate the spot price of rETH to steal funds.

A highly discounted rETH price would give rise to a small underlyingValue which in turn leads to a small preDepositPrice given the existing totalSupply which is assumed to be comparably large. When calculating totalStakeValueEth, assuming the attacker's ether deposit does not exceed the remaining rETH pool room, Reth::ethPerDerivative will this time use the correct rETH pricing which, combined with the small preDepositPrice, will result in a large number of SafEth tokens being minted. The attacker can then call SafEth::unstake with this large _safeEthAmount, withdrawing underlying ether from the derivative contracts for a profit and at the expense of other protocol participants.

Proof of Concept

  1. Let us first assume that the rETH pool is close to full, with a remaining room of say 200 ether which corresponds to the current max amount of the protocol (note that the owner has the ability to change this value). Assuming deposits totalling 100 ether are made to the rETH pool external to the protocol, and the derivative tokens have relative weights of 250, 250, 500 (WstETH, SfrxETH, Reth respectively), the first condition for this attack is met if protocol participants have deposited a total of 180 ether into the protocol. This means that the rETH pool is 10 ether short of being full but the protocol itself will still accept deposits of up to 20 ether.

  2. The second condition is met if the attacker can use a flash loan to manipulate the rETH spot price to be 0.1 ether, 10x lower than the current spot price of 1 ether which will result in them getting a far more favourable preDepositPrice . This is because the ethPerDerivative function will quote the UniswapV3 pool spot price if the derivative contract balance exceeds the remaining room in the rETH pool, which indeed it does given the prior deposits totalling 90 ether.

  3. The attacker can now deposit up to 20 ether to force usage of the actual rETH price function which will return approximately 1.06 and is negligible by comparison to the manipulated price above. This mints the attacker approximately 10x more SafEth tokens than should have been received by their deposit which they can then use to withdraw 200 ether from the protocol.

Severity Justification

This vulnerability allows an attacker to steal funds from the SafEth protocol with minimal required capital and so the severity is evaluated to HIGH.

Tools Used

Manual review

The SafEth::stake function should be updated to pass the correct _amount to IDerivative::ethPerDerivative which is the amount of ether being deposited. This will ensure that the correct rETH price is used when calculating the number of SafEth tokens to mint to the depositor. It should however be noted that use of spot prices in this manner is inherently risky and should be avoided so as to mitigate against other oracle manipulation attacks if/when a new deposit causes the rETH pool to become full.

#0 - c4-pre-sort

2023-04-04T17:52:07Z

0xSorryNotSorry marked the issue as duplicate of #1004

#1 - c4-judge

2023-04-21T14:06:43Z

Picodes marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter