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

Ability to Manipulate Airdrop Amount via Predictable Randomness

Description

  • Normally, airdrop amounts should be assigned to users in a way that is unpredictable and tamper-resistant.

  • The get_random_slice function currently uses the timestamp (timestamp::now_microseconds()) modulo 401 to assign claim amounts. Since blockchain timestamps are predictable or manipulable by validators, an attacker can calculate or influence the assigned amount, leading to unfair advantage or draining the pool.

// Root cause in the codebase with @> marks to highlight the relevant section
@> #[randomness]
@> entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
@> let state = borrow_global_mut<State>(get_resource_address());
@> let time = timestamp::now_microseconds(); // predictable source
@> let random_val = time % 401;
@> let random_amount = 100 + random_val;
@> table::add(&mut state.users_claimed_amount, user_addr, random_amount);
@> }

Risk

Likelihood:

  • Any user can call get_random_slice directly and predict the random amount.

  • Validators or miners can slightly manipulate timestamps to increase their assigned amount.

Impact:

  • Users can maximize their airdrop gain by predicting the output.

  • Attackers may unfairly drain the contract’s pool by repeatedly exploiting predictable randomness.


Proof of Concept

// Example sequence demonstrating exploit
// STEP 1: Predict the assigned amount
let predicted_time = timestamp::now_microseconds();
let predicted_amount = 100 + (predicted_time % 401);
// STEP 2: Call vulnerable function to self-assign the claim
0x6d3fd3c98cd61bf253b56bc5ff6f931be72c46b3b66259dc74c53322438fc349::airdrop::get_random_slice(attacker_addr);
// STEP 3: Claim the predicted amount
0x6d3fd3c98cd61bf253b56bc5ff6f931be72c46b3b66259dc74c53322438fc349::airdrop::claim_pizza_slice(attacker);

Recommended Mitigation

- #[randomness]
- entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
- let state = borrow_global_mut<State>(get_resource_address());
- let time = timestamp::now_microseconds();
- let random_val = time % 401;
- let random_amount = 100 + random_val;
- table::add(&mut state.users_claimed_amount, user_addr, random_amount);
- }
+ // Use a secure, cryptographically-random source for amounts
+ fun get_secure_random_slice(user_addr: address, seed: u64) acquires State {
+ let state = borrow_global_mut<State>(get_resource_address());
+ let random_val = std::hash::sha3_256(seed.to_bytes()) % 401; // pseudo-example
+ let random_amount = 100 + random_val;
+ if (!table::contains(&state.users_claimed_amount, user_addr)) {
+ table::add(&mut state.users_claimed_amount, user_addr, random_amount);
+ }
+ }

Effect of Mitigation:

  • Randomness becomes unpredictable and resistant to timestamp manipulation.

  • Attackers cannot reliably predict or influence the assigned airdrop amounts.

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.