Beginner FriendlyGameFi
100 EXP
View results
Submission Details
Severity: medium
Valid

Predictable Randomness (Timestamp-Based Random Number)

Root + Impact

Description

  • The contract uses timestamp::now_microseconds() % 401 + 100 to generate a “random” slice size (100–500 APT). This approach is not cryptographically secure. As documented by Aptos, using the current timestamp for randomness is vulnerable to manipulation: “a malicious user may bias the result by picking the transaction submission time; a malicious validator can bias the result by selecting which block the transaction goes to.”aptos.dev. In other words, the block proposer (validator) or the caller can influence timestamp::now_microseconds() to skew the outcome.

@> let random_val = time % 401;
@> let random_amount = 100 + random_val;

Risk

** Likelihood **:

  • Attackers (or even the owner/validator) can predict or influence the random value. For example, an attacker could delay or front-run the registration transaction to a time when time % 401 yields a high slice value. This undermines the fairness of the airdrop.

** Impact **:

  • Malicious actors may consistently receive larger slice amounts or ensure a desired outcome. This violates the stated “random surprise” promise. In the worst case, a validator could manipulate block timing so that a particular user always gets the maximum or some controlled value. The randomness becomes effectively deterministic for an attacker with sufficient control.

Proof of Concept

Suppose the attacker observes that time % 401 == 400 (max modulo result) yields the largest slice. The attacker submits the register_pizza_lover transaction exactly when now_microseconds() % 401 == 400. They will receive a slice of 500 APT. Repeating this (or a validator choosing when to include the transaction) can bias rewards. Example PoC assuming that get_random_slice is entry and uses timestamp::now_microseconds() to compute the random, so anyone can call it. If the attacker time submission for time % 401 == 400, they will be assigned the max slice:

// scripts/exploit_claim.move
script {
use std::signer;
use pizza_drop::airdrop;
fun main(attacker: &signer) {
// Get attacker address
let attacker_addr = signer::address_of(attacker);
// 1) Call the randomness entry - if it's an entry function anyone can call this,
// and it will add an assigned amount for attacker_addr using timestamp() % 401.
// On a vulnerable contract, the attacker times submission so the timestamp
// modulo yields the desired value (e.g., 400).
airdrop::get_random_slice(attacker_addr);
// 2) Claim the assigned amount (if the contract allows claim immediately)
airdrop::claim_pizza_slice(attacker);
}
}

Recommended Mitigation

Use a secure randomness source by implementing a commit-reveal scheme off-chain or use a verifiable random function (VRF). Or use an on-chain randomness API provided by APTOS (aptos_framework::randomness::u64_range) that delivers uniform, unbiased random values according to the documentation. For example:

let random_slice = aptos_framework::randomness::u64_range(0, 401) + 100;
Updates

Appeal created

bube Lead Judge 9 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Predictable randomness

The `get_random_slice` function should only be called by the owner via the `register_pizza_lover` function. Also, the `owner` is trusted and will not choose a specific time for a new user to register. Therefore, I disagree with the claim of most reports in this group that an attacker can manipulate the random number of pizza slices. But I agree with the root cause of the reports in this group, that the random distribution is not completely random.

Support

FAQs

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