[H-01] Anyone can change player-predicted results after they set it once
Summary
Players can pay for their prediction fee for each game and set their predictions using ThePredicter::makePrediction, but after that any one can just call the ScoreBoard::setPredictions to change the prediction set by players because it lacks access control.
Vulnerability Details
the setPredction function is created to allow players change their predictions, but since it lacks access control anyone can change the players predictions if they have payed for it beforehand.
function setPrediction(
address player,
uint256 matchNumber,
Result result
) public {
if (block.timestamp <= START_TIME + matchNumber * 68400 - 68400)
playersPredictions[player].predictions[matchNumber] = result;
playersPredictions[player].predictionsCount = 0;
for (uint256 i = 0; i < NUM_MATCHES; ++i) {
if (
playersPredictions[player].predictions[i] != Result.Pending &&
playersPredictions[player].isPaid[i]
) ++playersPredictions[player].predictionsCount;
}
}
Impact
A malicouse player/user can change others predictions and make themself the winner.
Proof of Concept
Add the following test to the exsiting test suit.
function test_ScoreCanBeChangedByAnyone() public {
vm.startPrank(stranger);
vm.deal(stranger, 0.0003 ether);
vm.stopPrank();
vm.warp(2);
vm.startPrank(organizer);
scoreBoard.setResult(0, ScoreBoard.Result.First);
vm.stopPrank();
vm.startPrank(stranger);
thePredicter.makePrediction{value: 0.0001 ether}(
0,
ScoreBoard.Result.First
);
vm.stopPrank();
ScoreBoard.Result result = scoreBoard.getPlayerResultForGame(
stranger,
0
);
assert(result == ScoreBoard.Result.First);
vm.prank(address(123));
scoreBoard.setPrediction(stranger, 0, ScoreBoard.Result.Second);
result = scoreBoard.getPlayerResultForGame(stranger, 0);
assert(result == ScoreBoard.Result.Second);
}
Tools Used
Manual Reivew
Recommendations
Add access control so that only the msg.sender can change their own predictions not others.
function setPrediction(
address player,
uint256 matchNumber,
Result result
) public {
+ if (msg.sender != player) revert();
if (block.timestamp <= START_TIME + matchNumber * 68400 - 68400)
playersPredictions[player].predictions[matchNumber] = result;
playersPredictions[player].predictionsCount = 0;
for (uint256 i = 0; i < NUM_MATCHES; ++i) {
if (
playersPredictions[player].predictions[i] != Result.Pending &&
playersPredictions[player].isPaid[i]
) ++playersPredictions[player].predictionsCount;
}
}