Bypassing Android Biometric Authentication

research

Mobile devices, such as smartphones, are increasingly omnipresent in today's world, with the Android operating system having a high popularity. The devices are used for online banking, instant messaging, or any other form of social networking. This raises the need to be able to provide applications that use state-of-the-art features to protect user data. To locally protect data, biometric authentication can be leveraged.

Biometric Authentication via handprint

We very often come across applications security assessments of mobile applications performed for our customers that implement biometric authentication. But it is often possible to bypass the authentication, as it is implemented in an insecure manner - and if some prerequisites are fulfilled. Therefore, it is important to stress in the beginning of this article that to be able to perform a bypass, an attacker needs root permissions on the device of the victim or is able to talk the victim into installing a modified version of an app with improper biometric authentication, and has physical access to the device in order to execute those attacks.

Previous research has shown that bypasses are possible, when biometric authentication is not implemented securely. This issue even affects apps that aim at offering a high data protection. In this blogpost we will shed some light on biometric authentication in Android written by our mobile security expert Leonard Eschenbaum and how it can be bypassed. This topic is also directly reflected in the OWASP Mobile Security Testing Guide (MASTG) in MSTG-AUTH-8 and as such is a mandatory issue to check for in mobile application security assessments.

Figure 1: BiometricPrompt architecture (Source: https://source.android.com/docs/security/features/biometric)
Figure 1: BiometricPrompt architecture (Source: https://source.android.com/docs/security/features/biometric)

Biometric Authentication in Android

A user trying to check their account balance does not want to have to enter their account password every time (especially since it should be a long and complex one). As such, biometric authentication offers a convenient way for users to authenticate. But it needs to be implemented securely, otherwise it might not protect against unauthorized access.

To use biometric authentication, Android offers two APIs:

  • FingerprintManager
  • BiometricPrompt

FingerprintManager was introduced with API level 23 (Android 6.0) but has been deprecated since API level 28 (Android 9). At that point, BiometricPrompt was introduced, which expands on its predecessor by not only supporting a fingerprint-based authentication, but also a face-based one. Nonetheless, FingerprintManager is still in use by many apps, as we see it a lot in our projects. In this blog post, however, we will focus on BiometricPrompt, as this is the current standard.

The figure below shows an overview of Android's biometric authentication architecture and how the different components work together. The diagram depicts the way applications interact with the BiometricPrompt API, which in turn is connected to other Android specific features, and specifically the KeyStore.

Authenticating Securely

BiometricPrompt provides a method called authenticate, which is used for displaying a biometric authentication dialog, and to start scanning for biometric data (e.g., fingerprint). To be precise, there are actually two authenticate methods available in BiometricPrompt: as an additional security layer, one of them expects a CryptoObject as input leveraging the Android KeyStore.

The Android KeyStore system stores and manages cryptographic keys inside the Trusted Execution Environment (TEE) of the Android system. Accessing data stored inside it requires specialized attacks that are very hard to execute, while using app-specific keys works by communicating with the KeyStore API.

The CryptoObject represents a wrapper class for cryptographic operations of the Java/Kotlin classes Signature, Cipher and Mac. To access protected data, these operations need to be executed. We find cases of Cipher operations in most of our security assessments, which is why we will focus on those in this blog post.

The authentication flow of the authenticate method requiring the CryptoObject is as follows:

  • The app creates a key that will be stored in the KeyStore with certain parameters set.
  • The key is used to encrypt information that authenticates the user (e.g. stored user credentials or a session token) or other data that should be protected.
  • Valid biometric data (e.g., fingerprint) must be presented before the key is released from the KeyStore to decrypt the data protected by the CryptoObject.

Certain security parameters can be set for the key:

  • isInvalidatedByBiometricEnrollment: ensures that a key will be invalidated as soon as new biometric data, e.g., a new fingerprint is enrolled on the device
  • isUserAuthenticationRequired: ensures that a key can only be used after successful validation of biometric user material (e.g., a fingerprint).

A callback to onAuthenticationSucceeded will be made after a successful authentication. This callback will then handle the CryptoObject (if any, depending on the authenticate overload) and the execution of the Cipher operation.

Bypassing an Insecure Implementation

We can use a Frida script in order to test an app for a secure implementation. Frida is a dynamic code instrumentation toolkit that can hook into functions of libraries or applications and inject code.

The script provides three authentication bypasses. It hooks into the authenticate method of the BiometricPrompt API to detect an authentication attempt. Inside the hook, the script will then make a callback to onAuthenticationSucceeded to trigger a successful authentication.

The bypasses work when either one of the three following conditions are met:

  1. No Use of Crypto: the app does not rely on cryptography.
  2. Improper Use of Crypto: the app uses improper settings for the key,
  3. Data not Crucial for Authentication: the protected data is not crucial for the authentication process.

These three cases are a result of one of the following issues:

No Use of Crypto

This case can be based on one of the following three sub-issues:

  • the app uses the authenticate overload that does NOT require a CryptoObject.
  • the app uses the authenticate overload that DOES require a CryptoObject, but it's set to null.
  • the app uses the authenticate overload that DOES require a CryptoObject, but the Cipher contained inside is set to null.

In all cases, the intercepted CryptoObject can just be forwarded.

Improper Use of Crypto

The key lacks a proper configuration by having the isUserAuthenticationRequired property set to false. This results in the Cipher operation being successfully executed unauthenticated.

Data not Crucial for Authentication

This case is sort of a special one. The app uses the authenticate overload that DOES require a CryptoObject AND the key is properly configured, but the encrypted data is not crucial for the authentication process. For example, the app might be encrypting/decrypting some informational or tracking value. When forwarding the intercepted CryptoObject, the app will try to execute the Cipher operation. However, since the CryptoObject is used unauthenticated (isUserAuthenticationRequred is set to true), this will trigger a javax.crypto.IllegalBlockSizeException. But again, Frida comes to our rescue: we can hook into the doFinal method of the Cipher class that is responsible for encryption/decryption and catch the exception to prevent the app from crashing.

Results of the Analysis

We used these approaches with high success in many different customer projects. But they’re also effective against very popular apps that aim to protect sensitive user data, such as password managers like Bitwarden or Dashlane, as well as instant messaging apps like Signal. No security review has been performed on those apps, they were just chosen as an example for the possible biometric authentication bypass because of improper implementation. These apps can be found on the Google Play Store. The following table gives a quick overview of some key facts of these apps.

App Downloads Version
Bitwarden 1M+ 2023.3.2
Dashlane 5M+ 6.2313.0
Signal 100M+ 6.17.3

As mentioned, biometric authentication in all three apps was bypassable. The table below summarizes the issues that were found in those apps and that enabled the bypass.

What this table shows is that Dashlane did not generate a key with all recommended security properties properly set, while Bitwarden and Signal did not generate a key at all and thus also did not make use of a CryptoObject to protect data cryptographically.

  Bitwarden Dashlane Signal
Improper Key Configuration   X  
No Key Generation X   X
No Use of CryptoObject X   X
Biometric Authentication Fingerprint

Conclusion

Cryptography and authentication issues are not only present in apps with a low number of downloads, but also in very popular apps. Furthermore, this affects also apps that aim to provide a high level of data protection, since they handle sensitive data that should be kept safe. To live up to their promise, they should be the first in line that follow security best practices to ensure just that.

However, it is important to stress that to be able to perform a bypass, an attacker needs root permissions on the device of the victim or is able to talk the victim into installing a modified version of an app with improper biometric authentication, and has physical access to the device in order to execute those attacks.

Due to these high obstacles, we decided to publish this blogpost already, while being currently in the process of communicating the identified issues to the developers. As of now, only Dashlane released a fix for the issue in version 6.2315.

This blog post has been written by Leonard Eschenbaum and published on behalf of the SEC Consult Vulnerability Lab.

Are you interested in working at SEC Consult?

SEC Consult is always searching for talented security professionals to work in our team.