Trick or Treat

First Flight #27
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: low
Invalid

Insecure Randomness Using Block Variables Allows Predictable Pricing Manipulation

The SpookySwap smart contract employs insecure sources of randomness—specifically block.timestamp and block.prevrandao—to determine pricing variations in its trickOrTreat function. This approach allows attackers to predict or manipulate the random number generation, enabling them to consistently receive favorable pricing outcomes, such as always getting half-price treats or avoiding double-price tricks. Consequently, this vulnerability can lead to significant financial losses for the contract owner and undermine the fairness of the platform.

Vulnerability Details

Random Number Generation in trickOrTreat

The contract's trickOrTreat function uses the following code to generate a pseudo-random number:

uint256 random =
uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, nextTokenId, block.prevrandao))) % 1000 + 1;

Components Used:

  • block.timestamp: The timestamp of the current block.

  • msg.sender: The address of the caller.

  • nextTokenId: A sequential token ID.

  • block.prevrandao: A value intended to introduce randomness, derived from the previous block.

Issues with the Randomness Sources

  1. block.timestamp

    • Manipulable by Miners/Validators: Miners or validators can slightly adjust the block timestamp within acceptable limits (~15 seconds). This small adjustment can influence the outcome of the random number generation.

    • Predictability: Since timestamps are generally sequential and predictable, they do not add sufficient entropy.

  2. block.prevrandao

    • Not Truly Random: While block.prevrandao adds some randomness, it can be influenced, especially if a validator is malicious or colluding with an attacker.

    • Limited Entropy: On its own, it may not provide enough unpredictability for secure random number generation.

  3. Combination of Predictable Variables

    • msg.sender and nextTokenId: Both are known values at the time of transaction submission.

    • Overall Predictability: Combining known and manipulable variables results in a random number that can be predicted or influenced by an attacker.

Potential for Exploitation

An attacker can:

  • Predict Random Outcomes: By knowing or controlling the input variables, an attacker can calculate the resulting random number before submitting a transaction.

  • Manipulate Block Variables: If the attacker is a miner or in collusion with one, they can adjust block.timestamp within permissible limits to achieve a desired outcome.

Impact

  • Economic Exploitation: Attackers can consistently obtain half-price treats or avoid double-price tricks, resulting in financial losses for the contract owner due to reduced revenues.

  • Unfair Advantage: Honest users are at a disadvantage as attackers manipulate the system to their benefit.

  • Reputation Damage: The platform's integrity may be questioned, leading to loss of user trust and decreased adoption.

  • Potential for Further Attacks: Predictable randomness can open doors to other vulnerabilities, such as front-running or denial-of-service attacks.

Proof of Concept

Scenario: Consistently Obtaining Half-Price Treats

An attacker aims to manipulate the random number generation to consistently receive the 1/1000 chance of getting a half-price treat.

Steps:

  1. Monitor the Blockchain State

    • Obtain the current nextTokenId from the contract.

    • Retrieve the block.prevrandao from the previous block.

  2. Predict the Random Number

    • For possible values of block.timestamp (within the miner's adjustable range), calculate the expected random number using the contract's formula.

    • Use known values: msg.sender (attacker's address) and nextTokenId.

  3. Adjust Transaction Timing

    • If the attacker is a miner/validator or collaborating with one, they can adjust block.timestamp within the allowed range to produce the desired random number.

    • If not, they can time their transaction submission to coincide with favorable conditions.

  4. Submit Transaction When random == 1

    • When calculations show that random will be 1, the attacker submits the trickOrTreat transaction.

    • The attacker pays half the price for the treat, exploiting the system.

Example Code:

const ethers = require('ethers');
// Known variables
const attackerAddress = '0xAttackerAddress';
const nextTokenId = 100; // Retrieved from the contract
const prevrandao = '0xPreviousRandao'; // From the blockchain
// Function to compute the random number
function computeRandom(blockTimestamp) {
const encoded = ethers.utils.defaultAbiCoder.encode(
['uint256', 'address', 'uint256', 'uint256'],
[blockTimestamp, attackerAddress, nextTokenId, prevrandao]
);
const hash = ethers.utils.keccak256(encoded);
const random = (BigInt(hash) % 1000n) + 1n;
return random;
}
// Simulate possible block timestamps
for (let i = currentTimestamp; i < currentTimestamp + 15; i++) {
const random = computeRandom(i);
if (random === 1n) {
console.log(`Block timestamp ${i} yields random == 1`);
// Submit transaction with adjusted timestamp
break;
}
}
Updates

Appeal created

bube Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

[invalid] Weak randomness

It's written in the README: "We're aware of the pseudorandom nature of the current implementation. This will be replaced with Chainlink VRF in later builds." This is a known issue.

Support

FAQs

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