Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Valid

Vulnerability 1

Root + Impact

Description

  • Marketplace should accept bids, keep the highest bid, refund the previous highest bidder when outbid, and transfer funds & NFT safely when an auction ends. Fee balance should be withdrawable only by owner.

  • The contract performs refunds / value transfers to previous bidders inside placeBid (push model) without pull-pattern and without sufficient reentrancy protection. A malicious bidder contract can either (a) revert on refund and block further bidding (DoS), or (b) reenter the marketplace to manipulate state and extract funds.

// Root cause in the codebase with @> marks to highlight the relevant section
@> function placeBid(uint256 tokenId) external payable {
@> Auction storage a = auctions[tokenId];
@> require(msg.value > a.highestBid, "Not higher");
@> // refund previous bidder (push)
@> if (a.highestBidder != address(0)) {
@> (bool sent, ) = a.highestBidder.call{value: a.highestBid}("");
@> require(sent, "refund failed");
@> }
@> a.highestBid = msg.value;
@> a.highestBidder = msg.sender;
@> }

Risk

Likelihood:

  • When the previous highest bidder is a malicious contract whose fallback/receive either reverts or uses gas-heavy logic — the refund call will revert or consume gas.

  • When a bidder contract’s fallback function reenters placeBid / endAuction (if there is no nonReentrant or state update ordering protection), an attacker can manipulate auction state mid-refund.

Impact:

  • Protocol-level DoS: Auctions become un-biddable or cannot proceed because refunds revert (blocked by the push refund call).

  • Reentrancy-based theft or state corruption resulting in lost funds or misdirected NFT ownership.

Proof of Concept

// Malicious bidder that reverts on refunds OR reenters marketplace
contract MaliciousBidder {
address marketplace;
uint tokenId;
constructor(address _marketplace, uint _tokenId) {
marketplace = _marketplace; tokenId = _tokenId;
}
// revert on receiving refunds -> causes push-refund call to revert
receive() external payable {
revert("no refunds");
}
function bid() external payable {
// call marketplace.placeBid{value: msg.value}(tokenId)
(bool s,) = marketplace.call{value: msg.value}(abi.encodeWithSignature("placeBid(uint256)", tokenId));
require(s);
}
}

Recommended Mitigation

Use a pull-based refund pattern: record pendingReturns[bidder] += previousBid and let bidders call withdraw() to retrieve funds. Avoid refunds inside placeBid.

  • If you must push funds, update state before external calls and use ReentrancyGuard (nonReentrant) around sensitive functions.

  • Add unit tests that include malicious bidder contracts that revert on receive and reenter to verify hardening.

- remove this code
+ add this code
Use a **pull-based refund pattern**: record `pendingReturns[bidder] += previousBid` and let bidders call `withdraw()` to retrieve funds. Avoid refunds inside `placeBid`.
* If you must push funds, update state **before** external calls and use `ReentrancyGuard` (`nonReentrant`) around sensitive functions.
* Add unit tests that include malicious bidder contracts that revert on receive and reenter to verify hardening.
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeast Marketplace: Reentrancy In PlaceBid

BidBeast Marketplace has a Medium-severity reentrancy vulnerability in its "buy-now" feature that allows an attacker to disrupt the platform by blocking sales or inflating gas fees for legitimate users.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.