Secret Vault on Aptos

First Flight #46
Beginner FriendlyWallet
100 EXP
View results
Submission Details
Severity: high
Valid

H03. Access Control for get_secret can be bypass

Root + Impact

Description

  • Normal behavior:

    • set_secret lets a signer create or update their own Vault containing a secret.

    • get_secret is supposed to allow only that same signer to retrieve it.

  • Specific issue:

    • get_secret takes an arbitrary caller: address parameter instead of being signer-bound.

    • The only check is assert!(caller == @owner, NOT_OWNER), which validates the argument against the stored vault but does not confirm that the transaction signer is the same as caller.

    • Any user can read another user’s secret simply by supplying their address.

// Root cause in the codebase with @> marks to highlight the relevant section
public fun get_secret(caller: address): String acquires Vault {
assert!(caller == @owner, NOT_OWNER);
// @> This compares input vs. vault owner
// @> but does NOT bind to the tx signer.
let vault = borrow_global<Vault>(@owner);
vault.secret
}

Risk

Likelihood:

  • Any external account can call get_secret(@victim) because it’s not restricted to a signer.

  • The module doesn’t use capabilities or ownership checks tied to the signer.

Impact:

  • All secrets stored with set_secret can be exfiltrated by anyone.

  • Breaks all confidentiality and any protocol relying on secure storage.


Proof of Concept

a)
Alice set the secret.
An attacker Bob can use the commande-line to retrieve the secret
aptos move view --function-id <program address>::vault::get_secret --args address:<owner address>

b) With code

// --- Step 1: Alice sets her secret ---
my_vault::set_secret(&alice, b"alice_password");
// --- Step 2: Bob (attacker) reads Alice's secret without permission ---
let stolen = my_vault::get_secret(@alice);
// stolen == "alice_password"
// Bob never needed Alice's signature to call get_secret.

Recommended Mitigation

Make get_secret signer-bound just like set_secret. Remove the untrusted caller parameter and read the vault based on address_of(user).

- public fun get_secret(caller: address): String acquires Vault {
- assert!(caller == @owner, NOT_OWNER);
- let vault = borrow_global<Vault>(@owner);
- vault.secret
- }
+ public fun get_secret(user: &signer): String acquires Vault {
+ let addr = address_of(user);
+ let vault = borrow_global<Vault>(addr);
+ vault.secret
+ }

This guarantees only the owner of a vault can retrieve its secret, matching the secure pattern already used by set_secret.

Reference: https://aptos.dev/build/smart-contracts/move-security-guidelines

Updates

Lead Judging Commences

bube Lead Judge 14 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of signer check in `get_secret`

Support

FAQs

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