Secret Vault

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

Hardcoded Owner Address Bypass in get_secret Function

Root Cause + Impact

Description

The get_secret function in the Secret Vault contract contains a critical access control vulnerability that completely undermines the security model of the application.

Normal Behavior

Only the owner should be able to set and retrieve their own secret from their personal vault storage.

Specific Issue

The get_secret function contains a logic error where it checks if the caller is the owner (@owner) but then always retrieves the vault from the hardcoded @owner address instead of the caller's address. This creates a fundamental mismatch between access control validation and data retrieval.

Vulnerable Code:

#[view]
public fun get_secret (caller: address):String acquires Vault{
assert! (caller == @owner,NOT_OWNER); // ✅ Checks if caller is @owner
let vault = borrow_global<Vault >(@owner); // ❌ Always reads from @owner's storage
vault.secret
}

Risk

Likelihood: High

  • Reason 1: The vulnerability is deterministic and always present - any call to get_secret with @owner as the caller parameter will trigger this behavior

  • Reason 2: The function is a public view function accessible to all users without additional restrictions

Impact: High

  • Impact 1: Complete bypass of the intended access control mechanism, violating the core security requirement

  • Impact 2: Unauthorized access to sensitive data - any user can retrieve the owner's secret by calling the function with the owner's address


Proof of Concept

Any external user can exploit this vulnerability with a simple function call:

// Malicious actor can retrieve the owner's secret:
let stolen_secret = get_secret(@owner); // This bypasses all intended security and returns the owner's secret

Attack Flow:

  1. Attacker calls get_secret(@owner)

  2. Function validates that @owner == @owner (passes ✅)

  3. Function retrieves vault from @owner's storage (unintended behavior ❌)

  4. Returns the owner's secret to the attacker

Code demonstrating the vulnerability:

// Root cause in the codebase with @> marks to highlight the relevant section
#[view]
public fun get_secret (caller: address):String acquires Vault{
assert! (caller == @owner,NOT_OWNER); // @> Check passes for @owner
let vault = borrow_global<Vault >(@owner); // @> Always reads from hardcoded @owner
vault.secret // @> Returns owner's secret regardless of intended access control
}

Recommended Mitigation

Fix the function to read from the caller's storage instead of the hardcoded owner address:

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

Alternative Solution: If the intention is to only allow the owner to access their own secret, consider redesigning the function to not take a caller parameter:

#[view]
public fun get_secret():String acquires Vault{
let vault = borrow_global<Vault >(@owner);
vault.secret
}

Summary

This vulnerability completely breaks the fundamental security promise of the Secret Vault application. The contract is designed to ensure that "only the owner should be able to store a secret and then retrieve it later," but this flaw allows anyone to access the owner's secret, making the entire security model ineffective.

Severity Classification: Critical/High - Complete access control bypass with unauthorized data disclosure.

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.