Bid Beasts

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

Anyone can steal failed credits via BidBeastsNFTMarketPlace.sol::withdrawAllFailedCredits()

Root + Impact

Description

The BidBeastsNFTMarketPlace.sol::withdrawAllFailedCredits() allows users with failed credits to withdraw them to a recipient address they prefer. However, the function incorrectly reads the credits of the receiver failedTransferCredits[_receiver], instead of the caller, which is wrong as receiver is not obligated to have failed credits - receiver just acts as a transfer destination. Also, the funds are never send to the receiver but to the caller. This flawed flow gives attackers a perfect opportunity to exploit it and steal funds.

Also, since the _receiver’s balance is never reduced, the same credits can be withdrawn multiple times, allowing an attacker to completely drain the contract’s ETH.

function withdrawAllFailedCredits(address _receiver) external {
@> uint256 amount = failedTransferCredits[_receiver]; //@audit - credits of receiver, should be msg.sender
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
@> (bool success, ) = payable(msg.sender).call{value: amount}(""); //@audit - send to sender != receiver
require(success, "Withdraw failed");
}

Risk

Likelihood:

High.

Impact:

  • Users failed credits can be stolen by malicious actors.

  • The market's ETH can be drained completely.

Proof of Concept

Consider the following example:

  1. Admin mints an NFT to Alice.

  2. Alice lists her NFT in the market.

  3. The auction begins and Bob places his bid.

  4. Another user comes and places a bigger bid - maybe wins the auction.

  5. Bob will be refunded his bid, but if something goes wrong, the tx wil not revert, but instead the failed credits of Bob will be increased, allowing him to redeem them later to a new receiver address via withdrawAllFailedCredits.

  6. An attacker can frontrun Bob and pass receiver = Bob.

  7. Bob's credit will be send to attacker.

This is just one attack path, but the failed credits can be stolen from the nft seller as well, or any user who has a record in the failedTransferCredits mapping.

Recommended Mitigation

Instead of reading the amount to withdraw from the receiver record => amount = failedTransferCredits[_receiver], use msg.sender and in the .call transfer the funds to the receiver address.

function withdrawAllFailedCredits(address _receiver) external {
- 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!