Secret Vault

First Flight #46
Beginner FriendlyWallet
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

M01. Anyone can set a secret

Root + Impact

Description

  • Normal behavior:
    set_secret should be callable only by the program owner (the module’s publishing address, e.g., @owner). This function configures a sensitive value, so unauthorized callers must be rejected.

  • Specific issue:
    set_secret is marked public entry and accepts any &signer. There is no check that address_of(caller) equals the program owner. As a result, any account can invoke set_secret, emit the SetNewSecret event, and write a Vault resource under their own address, spoofing configuration changes and polluting state.

// Root cause in the codebase with @> marks to highlight the relevant section
public entry fun set_secret(caller:&signer, secret:vector<u8>) {
let secret_vault = Vault{secret: string::utf8(secret)};
move_to(caller, secret_vault);
event::emit(SetNewSecret {});
// @> MISSING ACCESS CONTROL:
// @> No assertion that address_of(caller) == @owner (program owner)
}

Risk

Likelihood:

  • Whenever a non-owner externally calls set_secret, because the function is public entry and contains no owner/capability check.

  • Whenever off-chain indexers or other components trust the SetNewSecret event or the mere existence of Vault resources to infer official configuration.

Impact:

  • Unauthorized parties can trigger SetNewSecret events and create arbitrary Vault resources, spoofing configuration updates and potentially confusing integrators/UI/indexers.

  • State pollution / griefing by creating many bogus secrets under random addresses, increasing storage and complicating program logic that enumerates or validates vaults.

Proof of Concept

// Participants: @owner is the program owner; @attacker is an arbitrary account.
// --- Owner legitimately sets a secret (intended flow) ---
my_vault::set_secret(&owner, b"prod_secret");
// --- Attacker performs an unauthorized call (vulnerability) ---
my_vault::set_secret(&attacker, b"malicious_payload");
// Effects:
// 1) A SetNewSecret event is emitted by an unprivileged caller.
// 2) A Vault{ secret: "malicious_payload" } now exists under @attacker.
// Downstream tools that rely on events or scan for Vaults may treat this as an
// official update, enabling spoofing/phishing or operational confusion.

Recommended Mitigation

- public entry fun set_secret(caller:&signer, secret:vector<u8>){
- let secret_vault = Vault{secret: string::utf8(secret)};
- move_to(caller,secret_vault);
- event::emit(SetNewSecret {});
- }
+ const E_NOT_PROGRAM_OWNER: u64 = 0x1;
+
+ public entry fun set_secret(caller:&signer, secret:vector<u8>) {
+ // Enforce only the program owner can call this function
+ assert!(address_of(caller) == @owner, E_NOT_PROGRAM_OWNER);
+ let secret_vault = Vault { secret: string::utf8(secret) };
+ // Store under the owner's account (caller must be @owner due to the assert)
+ move_to(caller, secret_vault);
+ event::emit(SetNewSecret {});
+ }
Updates

Lead Judging Commences

bube Lead Judge 14 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Anyone can call `set_secret` function

In Move for Aptos, the term "owner" refers to a signer, which is a verified account that owns a given resource, has permission to add resources and the ability to grant access or modify digital assets. Following this logic in this contest, the owner is the account that owns `Vault`. This means that anyone has right to call `set_secret` and then to own the `Vault` and to retrieve the secret from the `Vault` in `get_secret` function. Therefore, this group is invalid, because the expected behavior is anyone to call the `set_secret` function.

Support

FAQs

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