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

Registration hijacking / griefing via public get_random_slice + table::add

Root + Impact

Description

  • get_random_slice is declared entry — any account can call it in a transaction.

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; // 100-500 APT (in Octas: 10^8 smallest unit)
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
  • get_random_slice computes random_amount and then does table::add(&mut state.users_claimed_amount, user_addr, random_amount); without checking if the key already exists.

  • register_pizza_lover is intended as owner-only registration, but it simply calls get_random_slice(user). Because get_random_slice is public, it can be executed by anyone (not necessarily the owner), before or instead of the owner.

  • table::add will abort if the key exists; it does not overwrite.

Risk

Likelihood:

  • get_random_slice is a public entry with no access control. No special privileges or complex conditions are required. Any account can call it and then call claim_pizza_slice.

Impact:

  • Full drain of the airdrop pool balance to attacker-controlled addresses

Proof of Concept

Self-registration & immediate drain (direct theft)

  1. Attacker calls get_random_slice(attacker_address) (public entry). This inserts (attacker_address → random_amount) into users_claimed_amount.

  2. Attacker calls claim_pizza_slice(&attacker_signer):

    • claim_pizza_slice checks table::contains(&state.users_claimed_amount, attacker_addr) → true.

    • claimed_users doesn't contain attacker_addr → true.

    • state.balance >= amount → if contract is funded, true.

    • The code registers account for AptosCoin if needed, calls transfer_from_contract(attacker_addr, amount) and marks claimed_users.

  3. Result: attacker receives APT from the resource account; owner control is bypassed.

Recommended Mitigation

Remove public access to get_random_slice. Make it a module-internal function (remove entry) and call only from register_pizza_lover which already requires state.owner

// make non-entry and internal
fun get_random_slice_internal(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;
// owner will call this internal helper only
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
Updates

Appeal created

bube Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

Anyone can call `get_random_slice` function

Support

FAQs

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