# 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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.