-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Attacker can drain all ETH from AuctionDemo when block.timestamp == auctionEndTime #1323
Comments
141345 marked the issue as duplicate of #1370 |
141345 marked the issue as duplicate of #962 |
alex-ppg marked the issue as not a duplicate |
alex-ppg marked the issue as duplicate of #1788 |
alex-ppg marked the issue as selected for report |
The Warden has illustrated that it is possible to exploit a time-based overlap between the claim of an auction and the cancellation of a bid to siphon funds from the NextGen system. In contrast to #175 which is based on a similar time overlap albeit in different functions, I deem it to be better suited as a "major" severity issue given that its likelihood is low but its impact is "critical", permitting up to the full auction amount to be stolen thereby tapping into funds of other users. The Warden's submission was selected as best because it clearly outlines the issue (it is possible to siphon all funds and not just claim the NFT for free), marks the timestamp caveat which limits the vulnerability's exploitability, and provides a short-and-sweet PoC illustrating the problem. |
alex-ppg marked the issue as satisfactory |
I initially planned to split cancel-after-bid submissions between re-entrancies and back-running attacks, however, both rely on the same core issue and as such will be merged under this exhibit given that back-running has "primacy" over re-entrancies (i.e. fixing a re-entrancy doesn't mean back-running is fixed, however, the opposite is true). |
Findings relating to the cancellation of a bid and immediate claim at a low price have been grouped under this finding as concerning the same root cause. For more information, consult the relevant response in the original primary exhibit. |
a2rocket (sponsor) confirmed |
Lines of code
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L105
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L116-L117
Vulnerability details
Description
It’s possible to drain all ETH from the
AuctionDemo
contract. There are two bugs that make it possible when they're exploited together.First bug:
In
AuctionDemo
there are two phases of an auction:minter.getAuctionEndTime(_tokenid)
, when users can callparticipateToAuction
andcancelBid
functionsminter.getAuctionEndTime(_tokenid)
, when the winner can callclaimAuction
However,
=<
and>=
inequalities are used in all checks (1, 2, 3). Therefore, ifblock.timestamp == minter.getAuctionEndTime(_tokenid)
then both phases are active at the same time and the winner of an auction can call all of these functions in the same transaction.On Ethereum blockchain, one block is produced every
12
seconds. Therefore, for each auction there’s1/12
probability thatminter.getAuctionEndTime(_tokenid)
will be exactly equal toblock.timestamp
for some block (assuming thatminter.getAuctionEndTime(_tokenid) % 12
is uniformly random).Second bug:
In
cancelBid
function,auctionInfoData[_tokenid][index].status
is set tofalse
when a user gets a refund. This is to prevent getting refund on the same bid multiple times.However, there is no such protection in
claimAuction
when refunding all non-winning bids.Exploit
These two bugs exploited together allow an attacker to quickly become the winner for an auction when
minter.getAuctionEndTime(_tokenid) == block.timestamp
, then place additional bid, and then get refunds on these bids by cancelling them.Refund on the winning bid causes the winner to get the auctioned NFT for free.
Refund on the additional bid, allows the attacker to drain the contract because they get back twice the deposited amount of ETH (one refund from
claimAuction
and one fromcancelBid
, both for the same bid).Impact
Attacker can “win“ an NFT from an auction and steal all ETH deposited for other auctions that are running at the time of the attack.
Condition that enables the attack is that
minter.getAuctionEndTime(_tokenid) == block.timestamp
for some block and for some_tokenid
.Let’s assume that:
minter.getAuctionEndTime(_tokenid) % 12
is a uniformly distributed random variable (all12
possible values have the same probability1/12
).12
seconds100
auctions running inAuctionDemo
(only some of them have to overlap in time).Under these assumptions, probability of
minter.getAuctionEndTime(_tokenid) == block.timestamp
occurring is1 - (11/12)^100 ~= 0.99983
. So it’s nearly guaranteed to happen.Proof of Concept
Exact steps and full code to reproduce the exploit are in this secret gist.
Here is the test with main logic of the exploit:
Tools Used
Foundry, manual analysis
Recommended Mitigation Steps
Use strict inequality (
>
instead of>=
) inclaimAuction
to exclude the exactminter.getAuctionEndTime(_tokenid)
from 2nd phase of the auction.Set
auctionInfoData[_tokenid][index].status = false
inclaimAuction
before sending refunds (just like incancelBid
) to protect against double refunds.Assessed type
Timing
The text was updated successfully, but these errors were encountered: