Platform: Code4rena
Start Date: 23/02/2024
Pot Size: $36,500 USDC
Total HM: 2
Participants: 39
Period: 7 days
Judge: Dravee
Id: 338
League: ETH
Rank: 2/39
Findings: 2
Award: $6,794.23
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Arabadzhiev
Also found by: ArmedGoose, blutorque
6774.638 USDC - $6,774.64
https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L609 https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L588
The PrincipalToken
allows for easier concentration of a underlying vault's shares in form of the ibt
. In the same time, it is possible to take a flashloan for all of the ibt
tokens. If enough vault's shares are concentrated in the PT, then it may enable vault reset attack, explained below, partially draining the underlying vault.
During the lifecycle of an ERC4626 vault, it's shares price related to underlying asset slowly increases. The initial ratio is usually 1 share per 1 unit of asset
, but over time, when vault's supplies grow, the shares become slightly more expensive and for example they cost 1 share for 1.3 or 1.5 unit of an asset
.
Now, considering references: Twitter thread by Kankodu, OZ issue 3800
The risk of "resetting" the vault exist, if vault shares liquidity is concentrated and someone could redeem most of the shares at once, e.g. by a flashloan, resetting the vault, and depositing assets at discounted price. Then, user can return the shares which are "bought" for less assets, repaying the flashloan and keeping the profit.
Since the protocol aims to gain popularity and each PT is dedicated to one specific vault, it should be considered a real risk, that if a vault becomes popular at a PT, which means, it's shares' liquidity is intensely concentrated in form of ibts, which is incentivized, then the vault may be attacked this way.
The flow of the attack is following:
1.5 per 1 asset unit
IBT
IBT
balance.IBT
in the underlying vault for assets, almost emptying the vault.IBTs
)total flashloan value in asset minus fees * (initial share price - reseted share price)
Manual approach.
If flashloans are to be used, consider limiting their amount to only a percentage of IBT balance, instead of full contract balance.
ERC4626
#0 - c4-pre-sort
2024-03-03T10:48:32Z
gzeon-c4 marked the issue as primary issue
#1 - c4-pre-sort
2024-03-03T10:48:36Z
gzeon-c4 marked the issue as high quality report
#2 - gzeon-c4
2024-03-03T10:49:24Z
attack likelihood is pretty low (require all ibt in this contract or the attacker have access to the rest)
#3 - c4-sponsor
2024-03-06T15:33:18Z
yanisepfl (sponsor) disputed
#4 - yanisepfl
2024-03-06T15:33:21Z
As the share price drops close to 1:1, user deposits assets again, gaining more than initially shares (IBTs)
Even if the liquidity was entirely concentrated in our protocol, and the IBT rate does indeed drop to 1, the user would get more IBTs than initially indeed, but those IBTs worth in assets will remain the same as the initial lower amount of IBTs with a higher rate.
Note: we have also considered many other attack vectors with the same hypothesis (where the IBTs are mostly concentrated in our PT vault) but different PoCs, and none of them lead to harming our protocol.
Therefore, the issue reported is wrong and disputed.
#5 - c4-judge
2024-03-11T01:10:50Z
JustDravee marked the issue as unsatisfactory: Invalid
#6 - Arabadzhiew
2024-03-12T08:27:20Z
Hey @JustDravee, thank you for your work!
I believe that this issue was misjudged. The following statement provided by the sponsor seems to be incorrect:
Even if the liquidity was entirely concentrated in our protocol, and the IBT rate does indeed drop to 1, the user would get more IBTs than initially indeed, but those IBTs worth in assets will remain the same as the initial lower amount of IBTs with a higher rate.
The core idea of this issue is that when a malicious user redeems all of the shares they borrowed and resets the IBT vault rate to 1, they are simply going to mint back an amount of IBT shares that is equal to the borrowed amount + flash loan fees, repay those to protocol and keep the difference in assets for themselves. They are not going to mint more shares at the lower rate and pay those back, since that is not required from them at all.
Issue #111 has a coded PoC that demonstrates exactly how this vulnerability can be exploited. I am also not sure why this issue (#240) was selected as the primary one instead of the one I just mentioned, but perhaps you can elaborate more on that.
#7 - blutorque
2024-03-12T17:36:20Z
Drop in IBTRate does profit the attacker and also do affect the protocol users.
Say you have 1000 IBTs backed by $2000 worth of USDT in an yield generating ERC4626 vault, the ratio of share to underlying is 1:2(which initially was 1:1 like any other vault).
As the sponsor said,
Note: we have also considered many other attack vectors with the same hypothesis (where the IBTs are mostly concentrated in our PT vault) but different PoCs, and none of them lead to harming our protocol.
Since, those IBTs are completely hold by PrincipalToken. Any user can claim the ownership of 1000 IBTs via flashloan, and redeem them all for the underlying. Now the attacker only required 1000 USDT to mint 1000 IBTs(since totalSupply==0), with which attacker can repay his debt.
So in total from IBTs vault, attacker manage to steal $1000 worth USDT.
My report #62 explains further ahead, How this actually affect the spectra user who have not claimed their yield yet.
Since, IBTRate drop back to 1. Attacker further calls updateYield
function for any user,
function updateYield(address _user) public override returns (uint256 updatedUserYieldInIBT) { (uint256 _ptRate, uint256 _ibtRate) = _updatePTandIBTRates(); ...snip...
The purpose of doing that is to update the global ptRate
and ibtRate
via _updatePTandIBTRates(),
Since, ibtRate was dropped by 50% already, the ptRate will also drop to 50%,
uint256 currentPTRate = currentIBTRate < ibtRate ? ptRate.mulDiv( currentIBTRate, ibtRate, roundUpPTRate ? Math.Rounding.Ceil : Math.Rounding.Floor )
t0 | t1 | t2 | |
---|---|---|---|
ptRate | 1 | 1 | 0.5 |
ibtRate | 1 | 2 | 1 |
This results in negative yield from ibtRate, https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/libraries/PrincipalTokenUtil.sol#L104-L107
as well as the less yield from the ptRate for user deposited at t1
,
https://github.com/code-423n4/2024-02-spectra/blob/383202d0b84985122fe1ba53cfbbb68f18ba3986/src/libraries/PrincipalTokenUtil.sol#L98-L103
#8 - kazantseff
2024-03-12T18:09:36Z
It looks like that this issue of ERC4626 vault price reset is the vulnerability in the implementation of the underlying ERC4626 vault itself, which should be out of scope, or am I missing something? It was also addressed and fixed here, it seems https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3800 https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3979
#9 - Arabadzhiew
2024-03-12T22:24:06Z
@kazantseff Quoting what is stated in the contest README:
The protocol is expected to interact with any ERC4626 compliant vault.
So essentially, what you are saying is the same as saying that vulnerabilities that are arising from ERC20 token integrations are out of scope for a given contest, since those are "vulnerabilities in the implementations of the underlying ERC20 tokens themself", even if it is said that the protocol is intended to interact with any ERC20 tokens.
Additionally, from what I can see this flaw is still present in the most recent ERC4626 OZ implementation (not that this matters).
#10 - kazantseff
2024-03-12T22:41:49Z
@Arabadzhiew
It was stated multiple times by a team that such issues are considered out of scope, since integrated IBTs will be monitored by the team, and it's up to the users to check if the IBT they put their money in is safe.
I feel like many issue could have been submitted based on vulnerabilities arising because of a 'wrong' or 'faulty' ERC4626 implementation, but at the end of the day we were auditing an ERC5095 PT contract, not an underlying vault it's being built on top, but that's just my opinion, the judge of course will have a final say.
#11 - Arabadzhiew
2024-03-12T22:57:01Z
You are referring to something completely different. The issue at question is not reliant on a malicious vault implementation at all. In fact, the issue will be present when integrating with completely normal and fair-functioning vaults. That is why I have used the OpenZeppelin ERC4626 implementation as an example in my report.
#12 - blutorque
2024-03-13T02:47:47Z
I feel like many issue could have been submitted based on vulnerabilities arising because of a 'wrong' or 'faulty' ERC4626 implementation,
But we never considered ERC4626 vault as faulty, as said before, either the supply is all in one place or the attacker own the whole,
Also check this comment, from sponser
Note: we have also considered many other attack vectors with the same hypothesis (where the IBTs are mostly concentrated in our PT vault) but different PoCs, and none of them lead to harming our protocol.
they tested the same hypothesis but unable to harm the protocol. We showed them how exactly under certain condition, this bug is exploitable.
Even with the legit vault, this could be a problem, also SS from contest channel
#13 - yanisepfl
2024-03-15T10:34:38Z
Hello all,
Thanks for the interesting conversation.
After discussing this issue and https://github.com/code-423n4/2024-02-spectra-findings/issues/111 with the team, we came to the conclusion that:
We therefore consider it a low or medium severity issue.
Concerning the mitigation, we believe there is no better solution than to have dead shares. The mitigation proposed in https://github.com/code-423n4/2024-02-spectra-findings/issues/111 is too restrictive (e.g. imprecisions) and the one proposed here wouldn’t work depending on the attacker’s PTs/YTs/IBTs ownership.
I hope this helps!
Edit: We acknowledge this issue. It will be clearly specified in our UI and documentations that users should be careful where they invest their funds. In particular, they are expected to make sure the IBTs follow the ERC 4626 and that their rate cannot be easily controlled by someone (e.g. price reset attack, see https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3800 & https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3979). In particular, the PoC provided here does not work on Open Zeppelin's 4626.
#14 - c4-judge
2024-03-15T11:34:32Z
JustDravee removed the grade
#15 - c4-judge
2024-03-15T11:35:03Z
JustDravee marked issue #111 as primary and marked this issue as a duplicate of 111
#16 - c4-judge
2024-03-15T12:00:31Z
JustDravee marked the issue as satisfactory
🌟 Selected for report: SBSecurity
Also found by: 0xDemon, 0xLuckyLuke, 14si2o_Flint, ArmedGoose, DarkTower, Shubham, Tigerfrake, cheatc0d3, peanuts, sl1
19.5868 USDC - $19.59
The PrincipalToken
is pausable however the pause stops the protocol completely. That means, even if users might want to redeem their funds, they will not be able to do so, which is a funds freeze for them, inflicting potential loss as they cannot withdraw / sell them.
However some protocols choose such design consciously, accepting the risk. In an ideal world, pause stops entering the protocol but allows for exiting it. But on the other hand, the complete emergency pause allows for stopping the whole protocol and potentially stop an entire attack.
Recommendation: Consider, if allowing users to exit the protocol during pause is acceptable for you.
claimFees
Function claimFees does not have option to specify or force a minimum amount accepted as fees. It just blindly redeems the ibts
equal to amount of fees, sets remaining fees to 0 and the recovered amount of assets is sent to fee collector.
However it should be kept in mind that redeeming from a vault might be subject to volatility, and ability to set a maximal slippage e.g. 5-10% would rule out a possibility where fee collector will redeem almost nothing due to slippage.
On the other hand, the amounts recovered from that source are probably not going to be significant, which might not incentive sandwichers, thus some losses may occur only due to organic volatility.
Recommendation: Consider adding ability to specify % slippage, minimal amount, or set it to auto, if fee claiming is meant to be autonomous.
The PrincipalToken
contract is used to transfer tokens to it. The flashloan function has a placeholder to custom token address. However, there is no utility to rescue tokens which might be sent to the contract. A function allowing the DAO for example to claim dust balance, but not considering IBTs or fees, might be helpful to prevent dust or mistakenly transferred tokens to be stuck on the contract.
#0 - c4-pre-sort
2024-03-03T13:55:13Z
gzeon-c4 marked the issue as sufficient quality report
#1 - c4-judge
2024-03-11T01:46:23Z
JustDravee marked the issue as grade-b