Bid Beasts

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

Reentrancy in placeBid()

Root + Impact

Description

Normally, placeBid() should safely handle bid updates and ETH transfers:

  • Refund the previous highest bidder.

  • Transfer the NFT and funds to the seller when a “buy-now” is triggered.

  • Update internal bid/listing state.

However, the function performs multiple external calls sending ETH** **(_payout() and _executeSale()) before all state updates are finalized.
These calls use low-level .call{value: amount}(""), which allows the recipient’s fallback to execute arbitrary code.
Because some state variables (bids[tokenId], listings[tokenId], failedTransferCredits) are modified after these calls, a malicious bidder can re-enter and call other public functions in the contract during the same transaction.

function placeBid(uint256 tokenId) external payable isListed(tokenId) {
...
@> _payout(previousBidder, previousBidAmount); // sends ETH before state changes are complete
@> _executeSale(tokenId); // transfers NFT and sends ETH; also updates bids[tokenId] after transfer
...
@> _payout(msg.sender, overpay); // another external call sending ETH
}

Risk

Likelihood:

  • Any previous bidder or seller with a contract wallet can supply a malicious fallback.

During these external calls they can re-enter other functions (e.g. unlistNFT, takeHighestBid, settleAuction) while bids/listings are in an inconsistent state.

Impact:

  • Manipulate auction results (e.g., cancel or replace bids mid-execution).

Withdraw funds multiple times or bypass minimum bid logic.

  • Potential theft of ETH or NFTs if reentrancy is exploited to re-trigger payout logic.

Proof of Concept

Deploy MaliciousBidder, call initialBid(), then have another user outbid it.
When placeBid() tries to refund the malicious bidder, the fallback fires and unlistNFT executes before placeBid finishes updating state.

// MaliciousBidder.sol
contract MaliciousBidder {
BidBeastsNFTMarket market;
uint256 tokenId;
constructor(BidBeastsNFTMarket _market, uint256 _tokenId) {
market = _market;
tokenId = _tokenId;
}
// Place an initial bid
function initialBid() external payable {
market.placeBid{value: msg.value}(tokenId);
}
// Fallback is triggered when refunded through _payout
receive() external payable {
// Re-enter critical function while market state not yet finalized
market.unlistNFT(tokenId); // or any other function that manipulates listings/bids
}
}

Recommended Mitigation

Alternatively, use ReentrancyGuard (nonReentrant) modifier from OpenZeppelin to block re-entrancy entirely.

Consider using the pull patern for all ETH transfers (users call a separate withdraw function).

- _payout(previousBidder, previousBidAmount);
- _executeSale(tokenId);
- _payout(msg.sender, overpay);
+ // ✅ Follow checks-effects-interactions
+ // 1. Update all internal state first.
+ bids[tokenId] = Bid(...);
+ listing.listed = false;
+ // 2. After state updates, perform external calls
+ _payout(previousBidder, previousBidAmount);
+ _executeSale(tokenId);
+ _payout(msg.sender, overpay);
Updates

Lead Judging Commences

cryptoghost Lead Judge 2 months 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.

Give us feedback!