Secret Vault on Aptos

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

Authentication Bypass in Access Control leads to Complete Authorization Failure

# Authentication Bypass in Access Control leads to Complete Authorization Failure

## Description

* The `get_secret()` function is designed to restrict access to only the owner by checking if the `caller` parameter equals `@owner`

* The critical flaw is that `get_secret()` is a view function that accepts an arbitrary `caller` parameter, allowing any user to simply pass `@owner` as the argument, making the authentication check meaningless since attackers control the input being validated

```java

#[view]

public fun get_secret(caller: address): String acquires Vault {

assert!(caller == @owner, NOT_OWNER); // @> Attacker can pass @owner directly

let vault = borrow_global<Vault>(@owner);

vault.secret // @> Access granted to anyone who passes @owner

}

```

## Risk

**Likelihood**:

* Any external caller can invoke `get_secret(@owner)` directly, bypassing authentication

* No actual verification of the caller's identity occurs - only validation of a user-controlled parameter

* The vulnerability is trivially exploitable with a single function call

**Impact**:

* Complete bypass of intended access controls for secret retrieval

* Unauthorized users gain full access to sensitive information through legitimate function calls

* Authentication mechanism provides false security, masking the actual vulnerability

* Could lead to privilege escalation if similar patterns exist elsewhere in the codebase

## Proof of Concept

* Add this test to `secret_vault.move` file and see the results

```java

#[test(owner = @0xcc)]

fun test_auth_bypass(owner: &signer) acquires Vault {

use aptos_framework::account;

account::create_account_for_test(signer::address_of(owner));

// Owner sets the secret

let secret = b"I Love ...";

set_secret(owner, secret);

// Attacker calls get_secret with caller=@owner

let leaked_secret = get_secret(@0xcc);

// Debug-print what attacker got

debug::print(&string::utf8(b"Attacker bypassed auth and got:"));

debug::print(&leaked_secret);

}

```

## Recommended Mitigation

  • Here is the recommended mitigation

```diff

- #[view]

- public fun get_secret(caller: address): String acquires Vault {

- assert!(caller == @owner, NOT_OWNER);

- let vault = borrow_global<Vault>(@owner);

- vault.secret

- }

+ // Option 1: Use &signer instead for authentication

+ public entry fun get_secret(caller: &signer): String acquires Vault {

+ assert!(signer::address_of(caller) == @owner, NOT_OWNER);

+ let vault = borrow_global<Vault>(@owner);

+ vault.secret

+ }

+

+ // Option 2: Remove the function entirely if not needed

+ // Since blockchain state is public anyway, this function provides no real value

+

```

Here's another bug write a proper separate report ok!

Proof Of Concept:

#[test(owner = @0xcc)]

fun test_set_secret_dos(owner: &signer) {

use aptos_framework::account;

account::create_account_for_test(signer::address_of(owner));

let secret1 = b"first-secret";

let secret2 = b"rotated-secret";

// First set_secret call should succeed

set_secret(owner, secret1);

// This proves the DoS bug

set_secret(owner, secret2);

}

5) DoS on set_secret due to unconditional move_to

move_to aborts if the resource already exists. After the first call, further updates will fail.

Impact: Owner can’t rotate/update their secret. Owner cant call set_secret twice after first time.

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.