withdrawAllFailedCredits reads the amount from failedTransferCredits[_receiver] but sets failedTransferCredits[msg.sender] = 0 and sends ETH to msg.sender. There is no authorization check that _receiver == msg.sender. The victim’s mapping entry is never cleared, allowing repeated withdrawals of the same amount. This is a logic/indexing bug in the withdraw function combined with missing ownership checks.Normal behavior: Users who have ETH credited to failedTransferCredits (due to previous failed payouts) should be able to withdraw only their own credited balance. A correct implementation reads the caller’s credited amount, zeroes that balance (effects), and then transfers the funds to the caller (interaction), protecting against reentrancy and emitting an event for auditability.
Specific issue: The function withdrawAllFailedCredits(address _receiver) reads the amount from failedTransferCredits[_receiver] but zeroes failedTransferCredits[msg.sender] and sends the ETH to msg.sender, without any authorization check that _receiver == msg.sender. This logic/indexing error lets any attacker request withdrawals for any victim and receive the victim’s credited amount into their own account while leaving the victim’s mapping unchanged — enabling repeated withdrawals of the same funds and potential full contract drain.
Likelihood:
When any address has a nonzero failedTransferCredits balance, any external account can call withdrawAllFailedCredits(address) with that address and receive the credited ETH while the victim’s mapping remains unchanged.
Because the mapping is public, automated bots or MEV operators can scan all credited balances and repeatedly exploit the function, making exploitation highly probable.
Impact:
Direct and repeated theft of credited funds: attackers can repeatedly withdraw another user’s credited ETH, causing immediate financial loss to users and depletion of contract funds.
Severe operational and reputational damage: legitimate withdrawals fail or are contested, marketplace operations are disrupted, and user trust and platform reputation suffer.
After successful execution of test the attaker's balance will be 4000000000000000000
because the rejector's address made a bid for 2 ethers
The mapping failedTransferCredits will check the amount in the context of msg.sender so if an address doesn't have the balance then the function will revert
The mapping failedTransferCredits will reduce in the context of msg.sender
I also recommend adding nonReentrant
because in future this contract might become vulnerable to cross function or cross contract reentrancy
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.