Secret Vault

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

**Broken Authentication in `get_secret` Function**

Root + Impact

Description

  • Describe the normal behavior in one or more sentences
    Answer: Normally, the get_secret function should only allow the designated owner of the vault to retrieve the stored secret, ensuring that no other account can access or read the sensitive data.


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

    The get_secret function takes an address as input and compares it to an undeclared @owner, meaning the check cannot reliably enforce ownership. Since any caller can supply the owner address as an argument, this effectively bypasses authentication and allows unauthorized users to read the secret.

#[view]
public fun get_secret(caller: address): String acquires Vault {
@> assert!(caller == @owner, NOT_OWNER);
@> let vault = borrow_global<Vault>(@owner);
vault.secret
}
@owner is undeclared in the module.
Ownership is enforced using a raw address argument (caller), which can be spoofed by anyone.

Risk

Likelihood:

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

    This occurs whenever a user calls get_secret and supplies the vault owner’s address as the caller parameter, since the function does not validate that the transaction signer actually controls that address.

  • Reason 2

    This occurs whenever the contract is deployed without defining a valid @owner constant, causing the ownership check to be meaningless and leaving the secret exposed to any address passed in as the caller.

Impact:

  • Impact 1

    Unauthorized users can retrieve and view the supposedly private secret, completely breaking the confidentiality the contract is meant to enforce.


  • Impact 2

    The contract fails its core security objective, undermining trust in the application and making it unsuitable for any real-world use where sensitive information must remain restricted to the owner.

Proof of Concept

Owner legitimately sets a secret.

Attacker calls get_secret, but since it only checks against a passed address, the attacker simply supplies @0xcc (owner’s address).

Function returns the secret to the attacker.

#[test_only]
module secret_vault::poc_exploit {
use secret_vault::vault;
use std::debug;
use aptos_framework::account;
#[test(owner = @0xcc, attacker = @0x123)]
fun test_exploit(owner: &signer, attacker: &signer) acquires vault::Vault {
// Step 1: Owner sets a secret
let secret = b"super_secret";
vault::set_secret(owner, secret);
// Step 2: Attacker calls `get_secret` pretending to be the owner
let leaked_secret = vault::get_secret(@0xcc); // passes owner's address
// Step 3: Print out the stolen secret
debug::print(&leaked_secret);
// Test demonstrates attacker has read the private data
}
}

Recommended Mitigation

Removes address parameter spoofing: now uses &signer so only the authenticated transaction sender can access their vault.

Eliminates undeclared @owner issue: uses signer::address_of(caller) directly.

Maintains confidentiality model: still worth noting that secrets are public on-chain; for real secrecy, owners should encrypt before storing.

- remove this code
- #[view]
- public fun get_secret(caller: address): String acquires Vault {
- assert!(caller == @owner, NOT_OWNER);
- let vault = borrow_global<Vault>(@owner);
- vault.secret
- }
+ add this code
+
+ /// Secure getter: only the signer who owns the vault can access their secret.
+ public entry fun get_secret(caller: &signer): vector<u8> acquires Vault {
+ let owner = signer::address_of(caller);
+ let vault = borrow_global<Vault>(owner);
+ // Return a copy of the secret as bytes
+ vault.secret
+ }
Updates

Lead Judging Commences

bube Lead Judge 13 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.