startGame()
function in the TwentyOne.sol
contract returns the player’s initial hand value as a uint256
. This allows attackers to manipulate gameplay by reverting unfavorable transactions, ensuring they proceed only with advantageous hands. By using a custom attack contract, an attacker can drain the contract's funds with minimal loss.The vulnerability lies in the startGame()
function, which exposes sensitive game state information (the player's initial hand value) as its return value. This design flaw enables attackers to evaluate the returned value and selectively revert transactions for unfavorable outcomes.
Initialization:
The attacker deploys and funds a malicious contract, AttackTwentyOne.sol
, which interacts with the TwentyOne.sol
contract.
Selective Execution:
The attack contract calls startGame()
, checks the returned hand value (playerHand
), and reverts if the value is below a predefined threshold (e.g., 20).
Guaranteed Advantage:
Transactions only proceed if the attacker’s hand value is sufficiently high, ensuring a disproportionately high winning probability.
Drain Contract:
The attacker continues exploiting until the target contract’s balance is depleted.
The issue is in the startGame()
function of TwentyOne.sol
:
Win Rate: The attack enables the attacker to win with an artificially high probability.
Drain Speed: The contract balance can be drained in approximately 1,500–2,000 calls, depending on the balance and win conditions.
Gas Costs: The attack incurs approximately 1,339,241 gas units per reverted transaction, which is minimal compared to the contract's drained value.
Solidity Programming: To analyze and manipulate the contract logic.
Foundry Framework: For contract deployment, testing, and debugging.
Python Script: To automate repeated calls for exploiting the vulnerability.
AttackTwentyOne.sol
)Deployment:
TwentyOne.sol
deployed with an initial balance of 20 ETH.
AttackTwentyOne.sol
deployed with an additional 2 ETH for gas and gameplay funding.
Execution:
Repeatedly called callTostartGameAndCall()
using the attack contract.
Observed selective transaction reverts for unfavorable outcomes.
Contract balance depleted after ~1,800 calls.
The startGame()
function should not return the player’s initial hand value, preventing attackers from accessing game state information during the initialization phase.
Removing the return value eliminates the ability to selectively revert transactions based on the initial hand, closing the exploit vector.
This report demonstrates a critical vulnerability in TwentyOne.sol
that enables an attacker to manipulate gameplay and drain the contract’s balance. The proposed fix mitigates this issue by removing sensitive game state exposure during the startGame()
phase. Immediate action is recommended to deploy the fix and secure funds in the contract.
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.