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

Unrestricted Resource Deletion

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

    Answer: A resource stored under a user’s account should only be deleted (moved out of storage) by its rightful owner, typically requiring the signer of the account.


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

    Answer: The contract allows any caller to delete another user’s resource because the delete_resource function accepts an arbitrary address instead of enforcing that only the account owner (via &signer) can perform the deletion.


module resource_leak::vault {
struct Vault has key {
balance: u64,
}
/// @> Problem: Anyone can delete a Vault at an arbitrary address
public entry fun delete_vault(addr: address) acquires Vault {
move_from<Vault>(addr);
}
}

Risk

Likelihood:

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

    Answer: This occurs whenever an attacker submits a transaction with the victim’s address.


  • Reason 2

    Answer: No authentication checks are made against &signer making it trivial to execute.

Impact:

  • Impact 1

    Answer: Complete loss of user resources (funds, data) because their Vault is deleted.


  • Impact 2

    Answer: Potential DoS (user’s vault cannot be accessed anymore).

Proof of Concept

In Step 1, the victim’s account stores a Vault.

In Step 2, the attacker provides the victim’s address (@0x111) to delete_vault. Because the function does not require the victim’s signer, the deletion succeeds.

In Step 3, the test confirms the vault is gone — demonstrating that any attacker can destroy another user’s resources.

#[test_only]
module resource_leak::poc_exploit {
use resource_leak::vault;
use std::debug;
use aptos_framework::account;
#[test(victim = @0x111, attacker = @0x222)]
fun test_delete_vault(victim: &signer, attacker: &signer) acquires vault::Vault {
// Step 1: Victim creates their Vault
move_to(victim, vault::Vault { balance: 100 });
// Step 2: Attacker maliciously calls delete_vault using victim’s address
vault::delete_vault(@0x111);
// Step 3: Attacker succeeds - victim’s vault is gone
let exists = exists<resource_leak::vault::Vault>(@0x111);
debug::print(&exists); // prints false, vault deleted
}
}

Recommended Mitigation

The new implementation requires a &signer, which is cryptographically tied to the transaction sender.

By deriving the address from signer::address_of(caller), the function ensures only the actual account owner can delete their own Vault.

This prevents attackers from passing arbitrary addresses and enforces strong ownership rules on resources — which is one of the key safety models of Move.

- remove this code
- public entry fun delete_vault(addr: address) acquires Vault {
- move_from<Vault>(addr);
- }
+ add this code
+ /// Secure deletion: only the signer who owns the vault can delete it
+ public entry fun delete_vault(caller: &signer) acquires Vault {
+ let owner = signer::address_of(caller);
+ move_from<Vault>(owner);
+ }
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.