Reentrancy in claimAuction()
#1772
Labels
3 (High Risk)
Assets can be stolen/lost/compromised directly
bug
Something isn't working
duplicate-1323
satisfactory
satisfies C4 submission criteria; eligible for awards
Lines of code
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L113
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L125
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L105
Vulnerability details
Impact
Reentrancy in
claimAuction()
allows a user to obtain an auctioned NFT for free by reenteringcancelBid()
while claiming the auction, essentially allowing an attacker to obtain the auctioned NFT, and then immediately obtaining its their amount refunded.Proof of Concept
When a user is the highest bidder, the
claimAuction()
must be triggered so that the NFT can be claimed, and the bid amount can be transferred to the contract owner:We can see how the previous block of code allows
block.timestamp
to be higher or equal to theminter.getAuctionEndTime(_tokenid)
. If now we analyze thecancelBid()
method, we'll see thatblock.timestamp
is required to be smaller or equal tominter.getAuctionEndTime(_tokenid)
. This essentially means thatcancelBid()
andclaimAuction()
can be called in the same block.An attacker can leverage the fact that such timestamp checks are poorly performed in order to craft a reentrancy attack, where the bid can be cancelled after obtaining the NFT for free, but before actually transferring the bid funds to the contract owner. The following steps illustrate how such attack would take place:
onERC721Received()
transfer hook) executes theclaimAuction()
function so that it can obtain the NFT, and such execution is triggered exactly at timestampminter.getAuctionEndTime(_tokenid)
safeTransferFrom()
transfer is performed, sending the NFT to the malicious highest bidder contract.onERC721Received()
transfer hook will then execute and reenter thecancelBid()
function. Becauseblock.timestamp
is equal tominter.getAuctionEndTime(_tokenid)
, the function will execute gracefully, effectively refunding the highest bidder with its bid ETH amount.cancelBid()
ends, execution will move back to the initialclaimAuction()
flow. Execution jumped when transferring the NFT from claiming the auction to cancelling the bid, so now that execution has been returned to claiming the auction, next step is to refund the owner. Because the contract holds ETH from other users and auctions, the owner will correctly obtain their corresponding highestBid amount, and execution will finish gracefully, effectively claiming an auctioned NFT while also obtaining the highest bid funds backTools Used
Manual review
Recommended Mitigation Steps
Add a reentrancy guard for
claimAuction()
. An effective way to add such checks is by using OpenZeppelin's Reentrancy guard.Assessed type
Reentrancy
The text was updated successfully, but these errors were encountered: