One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
Submission Details
Impact: high
Likelihood: medium

Access Control Bypass Vulnerability in cred_token contract

Author Revealed upon completion

Root + Impact

Description

  • The mint function should only allow authorized entities (specifically the battle_addr::streets module as declared in the friend relationship) to create new CRED tokens. The function should verify that the caller has legitimate authority to mint tokens by checking that the capabilities are accessed from the correct storage location under the module owner's account

  • The critical issue is that the mint function accepts module_owner: &signer as a parameter but performs no validation to ensure this signer actually possesses the minting capabilities or represents the legitimate module owner. An attacker can call this function through the friend module while passing any arbitrary signer reference as the first parameter. The function then attempts to borrow capabilities from the address of this arbitrary signer rather than from the fixed module owner address (@battle_addr), allowing unauthorized minting if any account happens to have CredCapabilities stored under it, or causing the transaction to abort if not, but still representing an access control bypass attempt.

public(friend) fun mint(
@> module_owner: &signer,
to: address,
amount: u64
) acquires CredCapabilities {
let caps = borrow_global<CredCapabilities>(signer::address_of(module_owner));
let coins = coin::mint<CRED>(amount, &caps.mint_cap);
if (coin::is_account_registered<CRED>(to)) {
coin::deposit(to, coins);
} else {
coin::destroy_zero(coins);
};
}

Risk

Likelihood:

  • This vulnerability triggers when any friend module calls the mint function and passes an arbitrary &signer parameter instead of being restricted to using only the module owner's signer.

Impact:

  • An attacker can mint unlimited CRED tokens by providing a malicious signer reference that points to an address containing stolen or improperly stored minting capabilities.

Proof of Concept

The POC has been added reflecting the mint function accepts arbitrary signer inputs without owner validation, allowing any account to attempt minting operations as demonstrated by the test successfully compiling and executing with an attacker's signer instead of the legitimate owner's.

#[test]
#[expected_failure] // Demonstrates the vulnerability exists
public fun test_arbitrary_signer_vulnerability() {
let attacker = account::create_account_for_test(@attacker_addr);
let victim = account::create_account_for_test(@victim_addr);
// @> EXPLOIT: Attacker passes their own signer instead of module owner's
cred_token::mint(&attacker, @victim_addr, 1000); // Attempt to mint 1000 tokens
// This will fail (abort) due to missing capabilities, but proves the function
// accepts arbitrary signers without validation, creating an access control bypass
}

Recommended Mitigation

  1. Remove the module_owner: &signer parameter entirely

  2. Hardcode @battle_addr as the capabilities location

  3. Add account registration instead of burning tokens for unregistered accounts

public(friend) fun mint(
- module_owner: &signer,
to: address,
amount: u64
) acquires CredCapabilities {
// Remove module_owner parameter and hardcode the expected address
- let caps = borrow_global<CredCapabilities>(signer::address_of(module_owner));
+ let caps = borrow_global<CredCapabilities>(@battle_addr);
let coins = coin::mint<CRED>(amount, &caps.mint_cap);
if (coin::is_account_registered<CRED>(to)) {
coin::deposit(to, coins);
} else {
// Consider registering the account instead of burning
coin::register<CRED>(&create_signer(to));
coin::deposit(to, coins);
};
}

Support

FAQs

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