A malicious user can win an auction and pull out their winning bid during the claimAuction call. This will invalidate the auction, owner() will not receive the winning amount and the user will not receive his NFT. This is possible due to reentrancy during the claimAuction call:
The main points are:
Reentrancy when calling claimAuction
And also the fact that cancelBid & cancelAllBids can be called at the same time as claimAuction due to a common moment in the conditions:
block.timestamp >= minter.getAuctionEndTime(_tokenid) and block.timestamp <= minter.getAuctionEndTime(_tokenid)
File: 2023-10-nextgen\hardhat\smart-contracts\AuctionDemo.sol
104: function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){
105: require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true);
File: 2023-10-nextgen\hardhat\smart-contracts\AuctionDemo.sol
124: function cancelBid(uint256 _tokenid, uint256 index) public {
125: require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
### Losses
Makes the auction invalid
## Proof of Concept
### Links
* https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L116
* https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124
* https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L134
### Description
This situation is possible if the user has an uncollected bet that he made before the winning one. Since this allows him to track the payout for the uncollected bid and call cancelBid to cancel the winning bid. This will cause the `claimAuction` to skip the part related to the issuance of the NFT and the payment to the owner
Example of behavior:
1. The user places their first bet for a minimum amount of 1 wei
2. The user places their second bet for a winning amount of 1 ether
3. When the `claimAuction` is called, the first bid will be refunded, since it was faster in order:
```js
File: 2023-10-nextgen\hardhat\smart-contracts\AuctionDemo.sol
115: } else if (auctionInfoData[_tokenid][i].status == true) {
116: (bool success, ) = payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}(""); // @audit reentracy
117: emit Refund(auctionInfoData[_tokenid][i].bidder, _tokenid, success, highestBid);
118: } else {}
This allows you to track the refund of the first bid and call cancelBid for the winning bid:
Change the condition so that it is not possible to execute transactions of completing the auction and cancelBid | cancelAllBid in it at the same time (block)
File: 2023-10-nextgen\hardhat\smart-contracts\AuctionDemo.sol
124: function cancelBid(uint256 _tokenid, uint256 index) public {
-125: require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
+125: require(block.timestamp < minter.getAuctionEndTime(_tokenid), "Auction ended");
139: function cancelAllBids(uint256 _tokenid) public {
-140: require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
+140: require(block.timestamp < minter.getAuctionEndTime(_tokenid), "Auction ended");
Lines of code
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L116 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L134
Vulnerability details
Impact
A malicious user can win an auction and pull out their winning bid during the
claimAuction
call. This will invalidate the auction,owner()
will not receive the winning amount and the user will not receive his NFT. This is possible due to reentrancy during theclaimAuction
call:The main points are:
claimAuction
cancelBid & cancelAllBids
can be called at the same time asclaimAuction
due to a common moment in the conditions:block.timestamp >= minter.getAuctionEndTime(_tokenid)
andblock.timestamp <= minter.getAuctionEndTime(_tokenid)
File: 2023-10-nextgen\hardhat\smart-contracts\AuctionDemo.sol 104: function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid,this.claimAuction.selector){ 105: require(block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false && minter.getAuctionStatus(_tokenid) == true);
File: 2023-10-nextgen\hardhat\smart-contracts\AuctionDemo.sol 124: function cancelBid(uint256 _tokenid, uint256 index) public { 125: require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
124: function cancelBid(uint256 _tokenid, uint256 index) public { // @audit call from L116 by reentracy and disable winner own bid 125: require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended"); 126: require(auctionInfoData[_tokenid][index].bidder == msg.sender && auctionInfoData[_tokenid][index].status == true); 127: auctionInfoData[_tokenid][index].status = false; 128: (bool success, ) = payable(auctionInfoData[_tokenid][index].bidder).call{value: auctionInfoData[_tokenid][index].bid}(""); 129: emit CancelBid(msg.sender, _tokenid, index, success, auctionInfoData[_tokenid][index].bid); 130: }
Tests
Tools Used
Recommended Mitigation Steps
Change the condition so that it is not possible to execute transactions of completing the auction and
cancelBid | cancelAllBid
in it at the same time (block)Assessed type
Invalid Validation