Platform: Code4rena
Start Date: 30/10/2023
Pot Size: $49,250 USDC
Total HM: 14
Participants: 243
Period: 14 days
Judge: 0xsomeone
Id: 302
League: ETH
Rank: 141/243
Findings: 2
Award: $2.77
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: smiling_heretic
Also found by: 00decree, 00xSEV, 0x180db, 0x3b, 0x656c68616a, 0xAadi, 0xAleko, 0xAsen, 0xDetermination, 0xJuda, 0xMAKEOUTHILL, 0xMango, 0xMosh, 0xSwahili, 0x_6a70, 0xarno, 0xgrbr, 0xpiken, 0xsagetony, 3th, 8olidity, ABA, AerialRaider, Al-Qa-qa, Arabadzhiev, AvantGard, CaeraDenoir, ChrisTina, DanielArmstrong, DarkTower, DeFiHackLabs, Deft_TT, Delvir0, Draiakoo, Eigenvectors, Fulum, Greed, HChang26, Haipls, Hama, Inference, Jiamin, JohnnyTime, Jorgect, Juntao, Kaysoft, Kose, Kow, Krace, MaNcHaSsS, Madalad, MrPotatoMagic, Neon2835, NoamYakov, Norah, Oxsadeeq, PENGUN, REKCAH, Ruhum, Shubham, Silvermist, Soul22, SovaSlava, SpicyMeatball, Talfao, TermoHash, The_Kakers, Toshii, TuringConsulting, Udsen, VAD37, Vagner, Zac, Zach_166, ZdravkoHr, _eperezok, ak1, aldarion, alexfilippov314, alexxander, amaechieth, aslanbek, ast3ros, audityourcontracts, ayden, bdmcbri, bird-flu, blutorque, bronze_pickaxe, btk, c0pp3rscr3w3r, c3phas, cartlex_, cccz, ciphermarco, circlelooper, crunch, cryptothemex, cu5t0mpeo, darksnow, degensec, dethera, devival, dimulski, droptpackets, epistkr, evmboi32, fibonacci, gumgumzum, immeas, innertia, inzinko, jasonxiale, joesan, ke1caM, kimchi, lanrebayode77, lsaudit, mahyar, max10afternoon, merlin, mrudenko, nuthan2x, oakcobalt, openwide, orion, phoenixV110, pontifex, r0ck3tz, rotcivegaf, rvierdiiev, seeques, shenwilly, sl1, slvDev, t0x1c, tallo, tnquanghuy0512, tpiliposian, trachev, twcctop, vangrim, volodya, xAriextz, xeros, xuwinnie, y4y, yobiz, zhaojie
0 USDC - $0.00
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#L125
The auction can be reentered, in some specific situations, which would cause a bidder to get back more ether than it should from the contract.
The bid system allows an user that bided already to get his share back by calling cancelBid
or cancelAllBids
if he bided multiple times. Both of them check if block.timestamp
is <= than getAuctionEndTime
as can be seen here
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#L135
and if that is the case, it sets the status
of the bidder to false
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L127
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L138
so when claimAuction
is called, the iteration would pass over the users that already cancel and claim their bids. The problem relies in the fact that claimAuction
checks for block.timestamp
to be >= than getAuctionEndTime
while cancelBid
and cancelAllBids
check for block.timestamp
to be <= than getAuctionEndTime
so there is a small timeframe where both statements would be true, when block.timestamp
is == to getAuctionEndTime
. In that scenario, when claimAuction
is called, because block.timestamp
is the same trough a whole transaction, a malicious user could have bided with a contract that looks like this
contract MaliciousBidder { uint256 tokenID; uint256 index; address AuctionAddress; function participate(uint256 tokenId, address x) public payable { Auction(x).participateToAuction{value : msg.value}(tokenId); } receive () payable external { Auction(AuctionAddress).cancelBid(tokenID,index); } }
which would basically call the cancelBid
function and all of the checks would pass since block.timestamp
would be == to getAuctionEndTime
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L125
msg.sender
would be the bidder and the status
is still set to true, since it is not changed in the claimAuction
function
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L126
In that way the bidder would get twice the amount of ether that he is supposed to, which would make other bidders lose their funds. Since the claimAuction
can be called also by the highest bidder, he could call the function exactly at block.timestamp == minter.getAuctionEndTime(_tokenid)
, in case he bided multiple times, so he can get more ether back thatn it should, so this makes it easily abusable.
Manual review
There are multiple ways to fix this, you can :
claimAuction
functionblock.timestamp
to be only < or > without the = sign, so the vulnerability is not achievableReentrancy
#0 - c4-pre-sort
2023-11-14T13:50:15Z
141345 marked the issue as duplicate of #289
#1 - c4-pre-sort
2023-11-14T23:32:25Z
141345 marked the issue as duplicate of #962
#2 - c4-judge
2023-12-04T21:41:04Z
alex-ppg marked the issue as duplicate of #1323
#3 - c4-judge
2023-12-08T18:05:30Z
alex-ppg marked the issue as satisfactory
#4 - c4-judge
2023-12-08T18:25:46Z
alex-ppg marked the issue as partial-50
🌟 Selected for report: The_Kakers
Also found by: 00xSEV, 0xAsen, 0xDetermination, 0xJuda, 0xWaitress, 0xhunter, 0xlemon, 0xpiken, Al-Qa-qa, Arabadzhiev, CSL, CaeraDenoir, DarkTower, DeFiHackLabs, Greed, Haipls, MaNcHaSsS, NentoR, NoamYakov, PENGUN, Ruhum, Soul22, SovaSlava, Talfao, Toshii, TuringConsulting, VAD37, Vagner, Valix, Viktor_Cortess, ZdravkoHr, audityourcontracts, btk, codynhat, flacko, funkornaut, glcanvas, gumgumzum, immeas, innertia, ke1caM, lanrebayode77, lsaudit, mrudenko, niki, nmirchev8, openwide, oualidpro, r0ck3tz, rvierdiiev, trachev, yojeff
2.7688 USDC - $2.77
The way the auction it is implemented right now in AuctionDemo.sol
can be easily DoS'ed blocking the whole claimAuction
mechanism.
The AuctionDemo.sol
is used to auction an NFTcreated in the MinterContract.sol
by calling mintAndAuction
function. After that, anyone can bid on it by calling participateToAuction
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L57-L61
where you need to pay more than the highest bidder, to get your data saved in the auctionInfoStru
. After the auction ends the highest bidder or the admin can call claimAuction
to iterate over the whole array of auctionInfoStru
saved, and transfer the NFT from the owner to the highest bidder and also transfer the ether sent by any other bidder back to themselves, as can be see here
https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L110-L119
The problem relies in the fact that this can be easily abusable, by forcing an OOG error and blocking the whole mechanism. To give you an example, I can participate in the early stages of auction, to pay a very low amount of ether, with a contract created by me which looks like this
contract MaliciousBidder { uint256[2000] public arr; function participate(uint256 tokenId, address x) public payable { Auction(x).participateToAuction{value : msg.value}(tokenId); } receive () payable external { // simulate a very expensive operation for (uint256 index = 0; index < 2000; index++) { arr[index] = index + 1; } } }
and when the claimAuction
function is called, because it iterates and tries to transfer the balances to every bidder, this would cost a lot of gas, more than the gas limit of the block which would force an OOG and block the auction forever. To give you some data about it, I tested with and array of 200 items, and that would cost around 4 million gas, only for the receive function, considering others operations/iterations done in the claimAuction
this would cause and OOG.
Manual review
My recommendation would be to use the Pull-over-Push method, let every bidder claim his share, and only transfer the NFT to the highest bidder and the ether to the owner in the claimAuction
, in that way you can't force and block the whole functionality of the contract.
DoS
#0 - c4-pre-sort
2023-11-16T10:37:52Z
141345 marked the issue as duplicate of #1952
#1 - c4-judge
2023-12-04T19:03:54Z
alex-ppg marked the issue as not a duplicate
#2 - c4-judge
2023-12-04T19:04:05Z
alex-ppg marked the issue as duplicate of #1782
#3 - c4-judge
2023-12-08T20:51:27Z
alex-ppg marked the issue as satisfactory