Normal behavior: When a direct ETH transfer fails (e.g., refund to previous bidder or seller proceeds), the contract should credit the intended recipient and later allow only that same recipient to withdraw their own credits. The withdrawal must decrement the credited balance to prevent repeated drains.
Issue: withdrawAllFailedCredits(address _receiver) reads the amount from failedTransferCredits[_receiver], but then zeros failedTransferCredits[msg.sender] (different key) and sends the funds to msg.sender. The victim’s credit under _receiver is never reduced, enabling any address to steal another user’s credits and repeat the withdrawal indefinitely (until the contract’s ETH balance is exhausted). Reentrancy isn’t required, but would amplify the drain.
Likelihood:
Occurs whenever any failed credits exist, e.g., a previous highest bidder or seller is a contract without a payable receive/fallback or one that reverts on ETH receipt (easy to engineer by bidding via such a contract).
Attackers simply call withdrawAllFailedCredits(victim) to siphon the victim’s credits to themselves; since the victim’s slot isn’t decremented, the attacker can repeatedly call and drain the contract’s ETH balance.
Impact:
Theft of funds: Credits belonging to other users are paid to the attacker.
Infinite drain: Because the victim’s credit is never decremented, repeated calls (or reentrancy) can drain all ETH held by the marketplace.
Attack flow example (end-to-end):
An address victim (e.g., a previous highest bidder using RejectEther) accumulates credits via _payout(victim, amount) → transfer fails → failedTransferCredits[victim] += amount.
Attacker calls withdrawAllFailedCredits(victim) once → receives amount to attacker, while failedTransferCredits[victim] remains unchanged.
Attacker repeats the call to siphon the same amount again, draining the contract’s ETH balance.
Additional hardening:
Add nonReentrant to the withdrawal function to block reentrancy amplification.
Keep CEI ordering (zero balance before external call).
Consider emitting an event FailedCreditsWithdrawn(msg.sender, amount) for auditability.
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.