After a user calls ThePredictor::withdraw
, the user should receive their ether reward. They should not be able to call withdraw again. The user not being able to call withdraw
again is meant to be handled by this external call:
Before funds are distributed to the user ScoreBoard::clearPredictionsCount
reduces the player's predictionsCount
to 0. In order for a user to be able to call withdraw, they need to pass this check which requires a prediction count > 0:
The malicious user is able to bypass this precaution by calling ScoreBoard::setPrediction
. After making this call, the user can call withdraw
and repeat this process until funds are drained from the contract.
The malicious user has made some predictions and is eligible to receive ether from ThePredicter::withdraw
All users are able to call ThePredicter::withdraw
after the organizer
has called ScoreBoard::setResult
for all matches
After calling withdraw
, the malicious user calls ScoreBoard::setPrediction
enabling them to call withdraw
again
The malicious user can repeat this process and receive ether from the contract until the contract can no longer disburse the withdraw::reward
amount
Users are meant to interact with ThePredciter::makePrediction
. This function has an important check
and makes external calls to the ScoreBoard
contract:
The user should only be able to make a prediction for a certain game at a certain time before the game. The user's information is passed on to the ScoreBoard
contract
An external call is made to ScoreBoard::setPrediction
. This is a public function that the user can call with arbitrary parameters whenever they want. It is meant to be used to update previous predictions without having to pay a predictionFee
. This check inside the function does not prevent function execution, it will only skip this particular block of code if time constraints are not met:
The rest of the function will execute regardless of the above timestamp check:
Even though ThePredicter::withdraw
uses scoreBoard.clearPredictionsCount(msg.sender);
to clear the user's predictionsCount
, the user can call ScoreBoard::setPrediction
with arbitrary parameters to restore their prediction count. With the predctionsCount
restored, the user can bypass the initial if (!scoreBoard.isEligibleForReward(msg.sender))
check in ThePredicter::withdraw
allowing them to repeat the process as many times as needed to extract the maximum amount from the contract
This test can be added to the provided test suite
Three participants enter the protocol
All three participants are eligible to receive 0.04 ether
stranger3
is able to call withdraw
multiple times by calling setPrediction
after each withdraw
call
The user could also use an attack contract to programmatically exploit the contract as opposed to making manual calls of ScoreBoard::setPrediction
This is a high impact vulnerability. While ScoreBoard::setPrediction
is meant to be public so users can make adjustments to predictions without paying fees, it lacks the same timestamp check as ThePredicter::withdraw
. Because of this inconsistency, users can still call this function when they should not be able to. This allows a user to potentially drain the contract by calling withdraw multiple times.
Manual Review
Foundry
Use a similar if check found in ThePredicter::withdraw
with ScoreBoard::setPrediction
:
In both contracts there is a similar error in the computation of the timestamps of the matches.
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.