An attacker can use a flaw in the FjordAuction
to steal user's rewards. The flaw is due to insufficient check in FjordeAuction::bid()
that doesn't check that the auction ended but instead checks if block.timestamp <= auctionEndTime
which is problematic when block.timestamp == auctionEndTime
and allows bids even when the auction ended.
Users are supposed to bid until the auction ends and after that they are eligable to claim their portion of the auctioned token. However there is a flaw that allows an attacker to steal honest users' rewards.
In FjordAuction::bid()
we have the following check that ensures there are no bids after the auction ended:
However this if
clause passes when block.timestamp = auctionEndTime
and if we look at the auctionEnd()
function we can see that:
It can be called by anyone
It allows the auction to end even when block.timestamp = auctionEndTime
This creates a scenario where an attacker can call auctionEnd()
exactly when block.timestamp = auctionEndTime
and after that place a bid using the bid
function which will execute successfully because of the above mentioned if
clause. Both of these transactions done in the same block. Since the multiplier
is calculated when the auction ends in auctionEnd()
it will not account for the new bid that the attacker placed and therefore allowing the attacker to call claim()
and claim another user's rewards.
Consider the following scenario:
Honest user (let's call him Bob) places a bid of 5e18 fjord points. (Assume that the total auctioned tokens = 5e18)
Now we are at the timestamp where an auction can end (block.timestamp = auctionEndTime)
An attcker calls auctionEnd()
and it sets the multiplier = 1e18
Exactly after that in the same block the attacker calls bid()
with 5e18 fjord points(he can put less tokens and still be able to steal the users' rewards but when the amount = totalBids, he gets all of the rewards) and it sets the bids
mapping.
The attacker front-runs or executes a claim transaction before Bob's.
The attacker gets all of the auctioned tokens and when Bob tries to claim his transaction reverts because the contract's balance has been emptied by the attacker
!NOTE
This attack can be performed with less fjord points. The attacker will gain hisBid * multiplier
and steal that many rewards from honest users.
Add the following test to 2024-08-fjord\test\unit\auction.t.sol
and add import "forge-std/console.sol";
at the top of the file. To run just do forge test --mt testAttackerStealsClaimableRewards -vvv
Users lose their share of the auctioned tokens
Manual Review, Foundry, VS Code
Change the FjordAuction::bid()
to the following:
The protocol doesn't properly treat the `block.timestamp == auctionEndTime` case. Impact: High - There are at least two possible impacts here: 1. By chance, user bids could land in a block after the `auctionEnd()` is called, not including them in the multiplier calculation, leading to a situation where there are insufficient funds to pay everyone's claim; 2. By malice, where someone can use a script to call `auctionEnd()` + `bid(totalBids)` + `claimTokens()`, effectively depriving all good faith bidders from tokens. Likelihood: Low – The chances of getting a `block.timestamp == auctionEndTime` are pretty slim, but it’s definitely possible.
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.