Skip to content

Latest commit

 

History

History
105 lines (64 loc) · 6.85 KB

0x05f-Testing-Local-Authentication.md

File metadata and controls

105 lines (64 loc) · 6.85 KB

Testing Local Authentication in Android Apps

Most of the authentication and session management requirements of the MASVS are generic ones, that do not rely on a specific implementation on iOS or Android.

As a result only requirement "4.6 Biometric authentication, if any, is not event-bound (i.e. using an API that simply returns "true" or "false"). Instead, it is based on unlocking the keychain/keystore." is described in this chapter. All other test need to verify server side implementations and can be found in the Appendix "Testing Authentication".

Testing Biometric Authentication

Overview

Android 6.0 introduced public APIs for authenticating users via fingerprint. Access to the fingerprint hardware is provided through the FingerprintManager class [1]. An app can request fingerprint authentication instantiating a FingerprintManager object and calling its authenticate() method. The caller registers callback methods to handle possible outcomes of the authentication process (success, failure or error).

By using the fingerprint API in conjunction with the Android KeyGenerator class, apps can create a cryptographic key that must be "unlocked" with the user's fingerprint. This can be used to implement more convenient forms of user login. For example, to allow users access to a remote service, a symmetric key can be created and used to encrypt the user PIN or authentication token. By calling setUserAuthenticationRequired(true) when creating the key, it is ensured that the user must re-authenticate using their fingerprint to retrieve it. The encrypted authentication data itself can then be saved using regular storage (e.g. SharedPreferences).

Apart from this relatively reasonable method, fingerprint authentication can also be implemented in unsafe ways. For instance, developers might opt to assume successful authentication based solely on whether the onAuthenticationSucceeded callback 3 is called. This event however isn't proof that the user has performed biometric authentication - such a check can be easily patched or bypassed using instrumentation. Leveraging the Keystore is the only way to be reasonably sure that the user has actually entered their fingerprint (unless of course, the Keystore is compromised).

Static Analysis

Search for calls of FingerprintManager.authenticate(). The first parameter passed to this method should be a CryptoObject instance. CryptoObject is a wrapper class for the crypto objects supported by FingerprintManager [2]. If this parameter is set to null, the fingerprint auth is purely event-bound, which likely causes a security issue.

Trace back the creation of the key used to initialize the cipher wrapped in the CryptoObject. Verify that the key was created using the KeyGenerator class, and that setUserAuthenticationRequired(true) was called when creating the KeyGenParameterSpec object (see also the code samples below).

Verify the authentication logic. For the authentication to be successful, the remote endpoint must require the client to present the secret retrieved from the Keystore, or some value derived from the secret.

Dynamic Analysis

Patch the app or us runtime instrumentation to bypass fingerprint authentication on the client. For example, you could use Frida call the onAuthenticationSucceeded callback directly. Refer to the chapter "Tampering and Reverse Engineering on Android" for more information.

Remediation

Fingerprint authentication should be implemented allong the following lines:

Check whether fingerprint authentication is possible. The device must run Android 6.0 or higher (SDK 23+) and feature a fingerprint sensor. The user must have protected their logscreen and registered at least one fingerprint on the device. If any of those checks failed, the option for fingerprint authentication should not be offered.

When setting up fingerprint authentication, create a new AES key using the KeyGenerator class. Add setUserAuthenticationRequired(true) in KeyGenParameterSpec.Builder.

	generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE);

	generator.init(new KeyGenParameterSpec.Builder (KEY_ALIAS,
	      KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
	      .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
	      .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
	      .setUserAuthenticationRequired(true)
	      .build()
	);

	generator.generateKey();

To perform encryption or decryption, create a Cipher object and initialize it with the AES key.

	SecretKey keyspec = (SecretKey)keyStore.getKey(KEY_ALIAS, null);

    if (mode == Cipher.ENCRYPT_MODE) {
        cipher.init(mode, keyspec);

Note that the key cannot be used right away - it has to be authenticated through FingerprintManager first. This involves wrapping Cipher into a FingerprintManager.CryptoObject which is passed to FingerprintManager.authenticate().

	cryptoObject = new FingerprintManager.CryptoObject(cipher);
	FingerprintHandler helper = new FingerprintHandler(this);
	helper.startAuth(fingerprintManager, cryptoObject);

If authentication succeeds, the callback method onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) is called, and the authenticated CryptoObject can be retrieved from the authentication result.

public void authenticationSucceeded(FingerprintManager.AuthenticationResult result) {
	cipher = result.getCryptoObject().getCipher();

	(... do something with the authenticated cipher object ...)
}

For a full example, see the blog article by Deivi Taka [4].

References

OWASP Mobile Top 10 2016
OWASP MASVS
  • 4.6: "Biometric authentication, if any, is not event-bound (i.e. using an API that simply returns "true" or "false"). Instead, it is based on unlocking the keychain/keystore."
CWE
  • CWE-287 - Improper Authentication
  • CWE-604 - Use of Client-Side Authentication
Info
Tools

N/A