Secret Vault

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

Unauthorized Secret Retrieval via Parameter Bypass in get_secret View Function

Root + Impact

Description

  • The SecretVault contract is designed to enable a single owner to securely store a secret on the Aptos blockchain via the set_secret entry function, which creates and moves a Vault resource containing the secret to the caller's account. The get_secret view function is intended to allow only the owner to retrieve this secret by verifying the provided caller address against the predefined @owner address and borrowing the resource from that address.

  • The get_secret function's access control is flawed because it relies solely on a user-supplied caller parameter for the ownership assert (assert!(caller == @owner, NOT_OWNER);), without requiring signer authentication, allowing any attacker to pass the owner's address as the parameter and bypass the check. Consequently, the function borrows and returns the secret from the hardcoded @owner address, enabling unauthorized users to steal the owner's secret without any restrictions or costs.

//// view functions
#[view]
public fun get_secret (caller: address):String acquires Vault{
// @> Root Cause Start: The assert relies on the user-supplied 'caller' parameter, which can be manipulated to match '@owner' without actual authentication.
assert! (caller == @owner,NOT_OWNER);
// @> This borrow is hardcoded to '@owner', exposing the secret if the assert is bypassed via the parameter.
let vault = borrow_global<Vault >(@owner);
vault.secret
// @> Root Cause End: No signer verification in this view function allows unauthorized calls to retrieve the secret by providing the correct address.
}

Risk

Likelihood:

  • Attackers invoke the public get_secret view function by supplying the owner's address as the caller parameter, which satisfies the assert check and returns the secret without requiring

  • The owner's address becomes discoverable through deployment details, on-chain queries, or public explorers, enabling exploitation by any user without additional privileges or costs.

Impact:

  • Unauthorized disclosure of the owner's stored secret occurs, compromising the confidentiality intended by the contract and exposing sensitive data to any caller.

  • Attackers exploit the leaked secret for further malicious actions, such as unauthorized access to related systems or financial theft if the secret holds value like a key or password.

Proof of Concept

#[test(owner = @0xcc, attacker = @0x42)]
fun exploit_unauthorized_secret_retrieval(owner: &signer, attacker: &signer) acquires Vault {
// 1. SETUP: Create accounts for owner and attacker
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(attacker));
// 2. SCENARIO: The owner sets a secret
let secret_to_set = b"my top secret value!";
set_secret(owner, secret_to_set);
let owner_address = signer::address_of(owner);
// 3. EXPLOIT: The attacker calls get_secret, passing the OWNER's address
// The check `caller == @owner` passes because the attacker provides
// the correct address as an argument, not as a signature.
let stolen_secret = get_secret(owner_address);
// 4. ASSERT: Verify the attacker successfully stole the secret
assert!(stolen_secret == string::utf8(secret_to_set), 5);
debug::print(&b"--> Exploit Successful: Attacker stole the owner's secret! <--");
}
}

This PoC demonstrates the unauthorized retrieval of the owner's secret by an attacker through the get_secret function. It uses a test function in Move to simulate the exploit in a controlled environment, confirming that the vulnerability allows any caller to bypass access controls by supplying the owner's address as a parameter.


Recommended Mitigation

To address the vulnerability, convert get_secret from a public view function (which lacks signer authentication) to an entry function that requires a signer for proper ownership verification. This prevents parameter manipulation


// events
#[event]
struct SetNewSecret has drop, store {
}
+ #[event]
+ struct GetSecret has drop, store {
+ secret: String
+ }
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 {});
}
//// view functions
- #[view]
- public fun get_secret (caller: address):String acquires Vault{
- assert! (caller == @owner,NOT_OWNER);
- let vault = borrow_global<Vault >(@owner);
-
- vault.secret
- }
+ public entry fun get_secret(caller: &signer) acquires Vault {
+ let addr = signer::address_of(caller);
+ assert!(addr == @owner, NOT_OWNER);
+ let vault = borrow_global<Vault>(@owner);
+ event::emit(GetSecret { secret: vault.secret });
+ }
Updates

Lead Judging Commences

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