Bid Beasts

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

Unauthorized withdrawal & persistent-credit drain due to parameter/mapping mismatch

Arbitrary address by an Attacker can cause loss of Funds!


Description:

The withdrawAllFailedCredits function contains a logic error where it reads the credited amount for an arbitrary address supplied by the caller (failedTransferCredits[_receiver]) but clears and pays out from/to the caller (failedTransferCredits[msg.sender] and payable(msg.sender)). Because the _receiver parameter is user-controlled, a caller can specify any victim address and the function will:

  • Read the victim’s stored credit amount into amount from failedTransferCredits[_receiver].

  • Clear the caller’s own mapping entry failedTransferCredits[msg.sender] (leaving the victim’s mapping untouched).

  • Transfer amount to the caller (msg.sender) regardless of ownership of that credit.

Crucially, the function never resets failedTransferCredits[_receiver] (the victim’s mapping entry) after transfer, so the same victim balance can be read and paid out repeatedly. This allows an attacker to repeatedly call the function with a victim’s address and continuously receive the victim’s recorded credit amount until the contract’s ETH balance is depleted/drained.

The mismatch between the mapping key used for reading (_receiver) and the mapping key used for clearing (msg.sender), combined with sending funds to the caller rather than the credited address, creates a direct theft vector: funds attributed to arbitrary accounts in the failedTransferCredits mapping can be siphoned by any caller. The only practical limit to exploitation is the contract’s available ETH balance.

This behaviour constitutes a direct, repeatable loss of funds for the contract and its users and therefore represents a high-risk, critical vulnerability.

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

Risk : Loss of Funds

Likelihood:

  • This will occur at a high likelihood as the address parameter is user controlled

  • Attacker can always steal funds

Impact:

  • Loss of precious Funds

  • Owners funds depleted / Whole Contracts ETH which came from Fees will be drained


Recommended Mitigation:

- function withdrawAllFailedCredits(address _receiver) external {
- uint256 amount = failedTransferCredits[_receiver];
- require(amount > 0, "No credits to withdraw");
-
- failedTransferCredits[msg.sender] = 0;
-
- (bool success, ) = payable(msg.sender).call{value: amount}("");
- require(success, "Withdraw failed");
- }
+ function withdrawAllFailedCredits(address _receiver) external nonReentrant {
+ require(_receiver == msg.sender, "Can only withdraw your own credits");
+ 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");
+ emit WithdrawFailedCredits(msg.sender, amount);
+ }
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!