get_random_slice
is declared entry
— any account can call it in a transaction.
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.
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
Self-registration & immediate drain (direct theft)
Attacker calls get_random_slice(attacker_address)
(public entry). This inserts (attacker_address → random_amount)
into users_claimed_amount
.
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
.
Result: attacker receives APT from the resource account; owner control is bypassed.
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
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.