This contract lets any user deposit assets using the briVault.deposit() function, which stores the assets staked to briVault.stakedAsset mapping, and mint shares accordingly.
However, within this same function, there's a weird mismatch that can lead to a severe vulnerability. Actually, the deposit() requires the user to pass a receiver address along with the assets amount.
On line 222, the contract maps the staked assets to the receiver using stakedAsset[receiver] = stakeAsset, whereas on line 231, it mints the shares to msg.sender using _mint(msg.sender, participantShares). Therefore, this will create a situation where assets are staked to one account, and shares are minted to the other.
Additionally, an attacker can take this to their own benefit due to the availability of the cancelParticipation() function, which lets anyone get their assets back by burning shares.
Likelihood: High
No restriction on third-party deposits (receiver ≠ msg.sender).
Easy to execute with two EOAs; no special timing required (just before event start).
Impact: High
The attacker gains profit, especially when they are the winner.
The attacker plays with a lower risk than others, since he has already gained his assets back. Doesn't have much to lose, even if he loses the bet.
Moreover, there are some chances of other withdrawers being denied their winner prize share.
Here's how the attack unfolds:
The Setup:
Attacker controls two users: user1 and user2.
Two other innocent users, user3 and user4 comes later into the picture.
Initiating the Exploit:
Through user1, attacker deposits 0.00025e18 tokens to the vault with receiver = user1. Thus minting shares to himself.
Next, again using user1, attacker deposits 5e18 tokens to the vault with receiver = user2. With this, shares again got minted to user1, but stakedAsset mapped those assets to user2, due to the vulnerable mismatch.
Then, user1 joins the event with the combined share value of both deposits made above.
The Twist:
Before even the event starts, user2 calls the cancelParticipation() function, which sent user2 the assets i.e. 5e18 - fee, and burnt the shares. But wait, user2 doesn't have any shares under his name, so it burned literally 0 shares.
However, the briVault.balanceOf(user1) still gives the same shares as before. The attacker has literally nothing to lose now, as he is refunded and is also part of the event with the same share amount.
How worse can it get?
Well, if the attacker becomes lucky and wins the event, then he will be getting assets of other withdrawers, that's because the vault will have more shares minted as compared to the totalAssets. So, it's obvious that the attacker will eat up other funds.
This can even lead to other winners being denied their withdrawals due to a low balance.
Add the test_UsersSharesNotBurnedButAssetsReturned test to briVault.t.sol:
Run the test using the following command:
Logs:
As mentioned, replace msg.sender with receiver on line 231
Harden cancelParticipation() so refunds are tied to the stake-backed shares and can’t be refunded with 0 burn:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.