Normal behavior: When a payout attempt to a recipient fails (for example, because the recipient is a contract that rejects ETH), the marketplace should credit the recipient with the failed amount and allow only that recipient to withdraw their credited funds later. Withdrawals must be secure so that only the rightful owner of the credit can claim it.
Issue: The withdrawAllFailedCredits function allows any caller to steal someone else’s credited ETH by passing the victim’s address as the _receiver parameter. The function reads the victim’s credited amount but then zeroes and pays out to msg.sender. This is a direct theft vulnerability.
Likelihood:
A deposit credit exists for a non-EOA recipient whenever _payout fails (for example, a contract that rejects ETH). That failure path is common for non-payable contracts.
Any attacker can call withdrawAllFailedCredits(victim) and receive the victim’s credited funds — no auth checks or preconditions are required.
Impact:
Immediate theft of any amount credited to failedTransferCredits[victim]. Funds intended for the victim will be transferred to the attacker.
Refunds and seller proceeds can be permanently lost to attackers, causing users to lose funds and the marketplace to become unusable for stakeholders with credited balances.
The test below follows your existing Forge test style. It demonstrates:
A contract RejectEther (no payable fallback) places an initial bid, causing that bidder to be refunded later.
The refund fails and the marketplace credits RejectEther in failedTransferCredits.
An attacker calls withdrawAllFailedCredits(rejector) and receives the credited ETH while the victim’s credit remains (or is left unchanged), i.e. theft occurs.
Expected outcome: The attacker (BIDDER_2) receives the MIN_PRICE amount that was credited to rejector, demonstrating theft.
Allow only the credited recipient to withdraw their own funds.
Zero the recipient’s credited balance before making the external call (checks-effects-interactions).
Optionally (strongly recommended): add nonReentrant from ReentrancyGuard to withdraw functions and other external-value-transfer functions.
Extra (recommended) hardening:
Requiring msg.sender to be the credited user prevents the theft scenario where an attacker asks the contract to give someone else’s credit to themselves.
Zeroing the balance before calling call follows checks-effects-interactions and prevents reentrancy from reclaiming the same balance multiple times.
nonReentrant adds another layer of protection if a recipient contract's fallback attempts any reentrant operations.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.