Beginner FriendlyGameFi
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Access control vulnerability

Root + Impact

The function transfer_from_contract() does not implement any access control, allowing anyone to move funds directly from the contract’s resource account.

Description

  • Normal behavior: The function should only be callable by owner to transfer tokens from the contract’s balance to users.

  • Issue: The function is declared public without restriction. It uses the stored signer_cap to create a signer for the contract’s resource account and then calls coin::transfer. Since there is no only_owner/access check, any external caller can execute this function and withdraw funds.

// Root cause in the codebase with @> marks to highlight the relevant section
fun transfer_from_contract(to: address, amount: u64) acquires ModuleData {
let module_data = borrow_global<ModuleData>(@pizza_drop);
let resource_signer = account::create_signer_with_capability(&module_data.signer_cap);
// @> Anyone calling this function can trigger coin::transfer
// @> No check on caller identity
coin::transfer<AptosCoin>(&resource_signer, to, amount);
}

Risk

Likelihood: High

  • Reason 1 // The function is callable by any external user since no restriction is enforced.

  • Reason 2

Impact: High

  • Impact 1 Unauthorized draining of the contract’s entire AptosCoin balance.

  • Impact 2

Proof of Concept

Attacker can call fun transfer_from_contract
Attacker chooses their own address as to
keeps calling until balance is drained

script {
use 0xYourModule::pizza_drop;
fun main(attacker: signer) {
pizza_drop::transfer_from_contract(@attacker, 1_000_000);
}
}

Recommended Mitigation

use Access control assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);

- fun transfer_from_contract(to: address, amount: u64) acquires ModuleData {
- let module_data = borrow_global<ModuleData>(@pizza_drop);
- let resource_signer = account::create_signer_with_capability(&module_data.signer_cap);
- coin::transfer<AptosCoin>(&resource_signer, to, amount);
- }
+ fun transfer_from_contract(caller: &signer, to: address, amount: u64) acquires ModuleData {
+ let module_data = borrow_global<ModuleData>(@pizza_drop);
+
+ // ✅ Restrict to contract admin only
+ assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);
+
+ let resource_signer = account::create_signer_with_capability(&module_data.signer_cap);
+ coin::transfer<AptosCoin>(&resource_signer, to, amount);
+ }
Updates

Appeal created

bube Lead Judge 12 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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