NextGen - 0x656c68616a's results

Advanced smart contracts for launching generative art projects on Ethereum.

General Information

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

NextGen

Findings Distribution

Researcher Performance

Rank: 239/243

Findings: 1

Award: $0.00

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 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

Awards

0 USDC - $0.00

Labels

bug
3 (High Risk)
partial-50
duplicate-1323

External Links

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L116 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L116

Vulnerability details

Vulnerability details

  • The vulnerability arises from a reentrancy issue in the interaction between the claimAuction and cancelAllBids functions. The critical flaw lies in allowing a participant, who has become the highest bidder and won the auction, to initiate a reentrancy attack during the block where block.timestamp == minter.getAuctionEndTime(_tokenid).

  • claimAuction function is designed to transfer the token to the winner and refund the other participants. Simultaneously.

 function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid, this.claimAuction.selector) {
        require(
            block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false
                && minter.getAuctionStatus(_tokenid) == true
        );
        auctionClaim[_tokenid] = true;
        uint256 highestBid = returnHighestBid(_tokenid);
        address ownerOfToken = IERC721(gencore).ownerOf(_tokenid);
        address highestBidder = returnHighestBidder(_tokenid);
        for (uint256 i = 0; i < auctionInfoData[_tokenid].length; i++) {
            if (
                auctionInfoData[_tokenid][i].bidder == highestBidder && auctionInfoData[_tokenid][i].bid == highestBid
                    && auctionInfoData[_tokenid][i].status == true
            ) {
                IERC721(gencore).safeTransferFrom(ownerOfToken, highestBidder, _tokenid);
                (bool success,) = payable(owner()).call{value: highestBid}("");
                emit ClaimAuction(owner(), _tokenid, success, highestBid);
    >>         } else if (auctionInfoData[_tokenid][i].status == true) {
                (bool success,) =
                    payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}("");
                emit Refund(auctionInfoData[_tokenid][i].bidder, _tokenid, success, highestBid);
            } else {}
        }
    }

notice that it's doesn't set auctionInfoData[_tokenid][i].status to false after sending bids to the bidders.

  • cancelAllBids function allows a bidder to cancel all of his bids it send eth to the biddes and set auctionInfoData[_tokenid][i].status to false for each bid get cancled. and it also can be called when block.timestamp == minter.getAuctionEndTime(_tokenid)
 function cancelAllBids(uint256 _tokenid) public {
        require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
        for (uint256 i = 0; i < auctionInfoData[_tokenid].length; i++) {
    >>         if (auctionInfoData[_tokenid][i].bidder == msg.sender && auctionInfoData[_tokenid][i].status == true) {
                auctionInfoData[_tokenid][i].status = false;
                (bool success,) =
                    payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}("");
                emit CancelBid(msg.sender, _tokenid, i, success, auctionInfoData[_tokenid][i].bid);
            } else {}
        }
    }

notice that for order to cancle bids the status should be true.

  • so in case the winner calls claimAuction when block.timestamp == minter.getAuctionEndTime(_tokenid), and he have other bids beside the higher bid. he can reenter the contract when the contract send him eth . and call cancleAllBids which will refund him again.

impact

  • The attacker can exploit the auction mechanism to receive a double payout, comprising funds from other users, including those involved in different token auctions. This results in a direct financial loss for participants .

Proof of Concept

  • when a token added to the auction an attacker . will bid with multiple bids.
  • the attacker then will target the block when block.timestamp == minter.getAuctionEndTime(_tokenid) and in the same block will do the following :
    • Participate to this token auction, so he will be the highst bidder.
    • call claimAuction function .
  • the function then will start looping thourgh all the bidders and send them eth. and when the contracts reach the last bidder (which always will be the higher bid) it send the amont of bid to the owner of token , and send the token to this higher bidder .
  • let's say the attacker have 10 bids in this token auction and the higher bid, the contract will start sending him eth for each bid. the attacker will check when the contract send him the 10th bid and call cancelAllBids.
  • the contract will send him again all his bid Including the highest bid since :
    • auctionInfoData[_tokenid][i].bidder == msg.sender && auctionInfoData[_tokenid][i].status == true So the attacker will get refunded two times for his 10 bids , and 1 time highest bid.
  • the token will not get sent to the attacker. since his bids all set to false. and the auction end.
  • Notice if theattacker didn't success to catch the block when block.timestamp == minter.getAuctionEndTime(_tokenid), he will just revert the Tx , and he still get refunded his bids when the winner or the admin calls claimAuction.

tool used

vs code

  • in the function claimAuction make this change :
 function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid, this.claimAuction.selector) {
        require(
            // set change this to > instead of >= :
            block.timestamp > minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false
                && minter.getAuctionStatus(_tokenid) == true
        );
        ......
        ......
 }

Assessed type

Reentrancy

#0 - c4-pre-sort

2023-11-20T14:25:18Z

141345 marked the issue as duplicate of #962

#1 - c4-judge

2023-12-04T21:40:40Z

alex-ppg marked the issue as duplicate of #1323

#2 - c4-judge

2023-12-08T18:12:22Z

alex-ppg marked the issue as partial-50

#3 - alex-ppg

2023-12-08T18:12:54Z

This is the same submission as #862 by the same Warden, perhaps either should be removed/nullified.

Findings Information

🌟 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

Awards

0 USDC - $0.00

Labels

bug
3 (High Risk)
partial-50
duplicate-1323

External Links

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L116 https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L116

Vulnerability details

Vulnerability details

  • The vulnerability arises from a reentrancy issue in the interaction between the claimAuction and cancelAllBids functions. The critical flaw lies in allowing a participant, who has become the highest bidder and won the auction, to initiate a reentrancy attack during the block where block.timestamp == minter.getAuctionEndTime(_tokenid).

  • claimAuction function is designed to transfer the token to the winner and refund the other participants. Simultaneously.

 function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid, this.claimAuction.selector) {
        require(
            block.timestamp >= minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false
                && minter.getAuctionStatus(_tokenid) == true
        );
        auctionClaim[_tokenid] = true;
        uint256 highestBid = returnHighestBid(_tokenid);
        address ownerOfToken = IERC721(gencore).ownerOf(_tokenid);
        address highestBidder = returnHighestBidder(_tokenid);
        for (uint256 i = 0; i < auctionInfoData[_tokenid].length; i++) {
            if (
                auctionInfoData[_tokenid][i].bidder == highestBidder && auctionInfoData[_tokenid][i].bid == highestBid
                    && auctionInfoData[_tokenid][i].status == true
            ) {
                IERC721(gencore).safeTransferFrom(ownerOfToken, highestBidder, _tokenid);
                (bool success,) = payable(owner()).call{value: highestBid}("");
                emit ClaimAuction(owner(), _tokenid, success, highestBid);
    >>         } else if (auctionInfoData[_tokenid][i].status == true) {
                (bool success,) =
                    payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}("");
                emit Refund(auctionInfoData[_tokenid][i].bidder, _tokenid, success, highestBid);
            } else {}
        }
    }

notice that it's doesn't set auctionInfoData[_tokenid][i].status to false after sending bids to the bidders.

  • cancelAllBids function allows a bidder to cancel all of his bids it send eth to the biddes and set auctionInfoData[_tokenid][i].status to false for each bid get cancled. and it also can be called when block.timestamp == minter.getAuctionEndTime(_tokenid)
 function cancelAllBids(uint256 _tokenid) public {
        require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
        for (uint256 i = 0; i < auctionInfoData[_tokenid].length; i++) {
    >>         if (auctionInfoData[_tokenid][i].bidder == msg.sender && auctionInfoData[_tokenid][i].status == true) {
                auctionInfoData[_tokenid][i].status = false;
                (bool success,) =
                    payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}("");
                emit CancelBid(msg.sender, _tokenid, i, success, auctionInfoData[_tokenid][i].bid);
            } else {}
        }
    }

notice that for order to cancle bids the status should be true.

  • so in case the winner calls claimAuction when block.timestamp == minter.getAuctionEndTime(_tokenid), and he have other bids beside the higher bid. he can reenter the contract when the contract send him eth . and call cancleAllBids which will refund him again.

impact

  • The attacker can exploit the auction mechanism to receive a double payout, comprising funds from other users, including those involved in different token auctions. This results in a direct financial loss for participants .

Proof of Concept

  • when a token added to the auction an attacker . will bid with multiple bids.
  • the attacker then will target the block when block.timestamp == minter.getAuctionEndTime(_tokenid) and in the same block will do the following :
    • Participate to this token auction, so he will be the highst bidder.
    • call claimAuction function .
  • the function then will start looping thourgh all the bidders and send them eth. and when the contracts reach the last bidder (which always will be the higher bid) it send the amont of bid to the owner of token , and send the token to this higher bidder .
  • let's say the attacker have 10 bids in this token auction and the higher bid, the contract will start sending him eth for each bid. the attacker will check when the contract send him the 10th bid and call cancelAllBids.
  • the contract will send him again all his bid Including the highest bid since :
    • auctionInfoData[_tokenid][i].bidder == msg.sender && auctionInfoData[_tokenid][i].status == true So the attacker will get refunded two times for his 10 bids , and 1 time highest bid.
  • the token will not get sent to the attacker. since his bids all set to false. and the auction end.
  • Notice if theattacker didn't success to catch the block when block.timestamp == minter.getAuctionEndTime(_tokenid), he will just revert the Tx , and he still get refunded his bids when the winner or the admin calls claimAuction.

tool used

vs code

  • in the function claimAuction make this change :
 function claimAuction(uint256 _tokenid) public WinnerOrAdminRequired(_tokenid, this.claimAuction.selector) {
        require(
            // set change this to > instead of >= :
            block.timestamp > minter.getAuctionEndTime(_tokenid) && auctionClaim[_tokenid] == false
                && minter.getAuctionStatus(_tokenid) == true
        );
        ......
        ......
 }

Assessed type

Reentrancy

#0 - c4-pre-sort

2023-11-20T14:25:36Z

141345 marked the issue as duplicate of #962

#1 - c4-judge

2023-12-04T21:40:42Z

alex-ppg marked the issue as duplicate of #1323

#2 - c4-judge

2023-12-08T18:11:58Z

alex-ppg marked the issue as partial-50

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter