As mobile devices and mobile applications increase, so does the risk of using mobile applications unsafely. I’ve come across multiple projects that also included Mobile Applications that needed to be tested (both iOS and Android). Most of the time, the applications come with a Biometric Authentication method, be it Fingerprint/Touch ID or FaceID.
Biometric authentication, as its name implies, is a client-side authentication method that relies on the user’s biometric data, be it his fingerprint or face.
Using biometrics features like Touch ID and Face ID is a convenient way to authenticate a user before performing sensitive actions. These actions depend on the application’s features (banking applications, e-commerce applications, etc). But can you say with 100% confidence that this type of authentication is secure?
The answer is of course not. The vast majority of biometrics checks are performed locally, on your device, and like any other ‘client-side checks’ can be bypassed if the attacker can control the application/device. Of course, there are methods to check whether the biometric used is genuine or not on the application’s backend side.
As I already mentioned, no authentication method is 100% secure, but you can take multiple actions to go closer to that 100% score. In this article, I want to show you how to bypass biometric authentication on iOS and Android. Let’s start with the Android one, but before jumping to the actual exploits, let’s create some background.
The Android Keystore is a system that allows developers to create and store cryptographic keys in a secure container, making them difficult to extract from the device, even using advanced techniques. These cryptographic keys are stored within specialized hardware known as TEE(Trusted Execution Environment). It is quite difficult to extract the keys stored in this TEE. Even the operating system does not have direct access to this secure memory. To perform the cryptographic operations and to receive the result within this secured environment, AndroidKeystore provides APIs to manage access.
Fingerprint authentication can be implemented using either the FingerprintManager or BiometricPrompt and nested classes that manage the authentication mechanism, and an application dialogue asking the user to authenticate. Any of the aforementioned classes are used for application authentication, allowing the user to set the device’s biometric as a replacement for the username/password flow. After successful login using username and password, the user can choose to authenticate with the device’s biometric, thus the application creates an object that is placed in the Keystore and accessed when the fingerprint is valid. As a side note, FingerprintManager and FingerprintManagerCompact are deprecated since API 28 (Android 9).
If you have already read any of my previous posts, you saw that I am a big fan of Frida and how it operates. If not, please have a look at the article that presents this amazing tool. Nevertheless, before launching the exploits, multiple prerequisites should be in place:
As you might imagine, there are multiple ways of bypassing the Android Biometric Authentication. One requires the lack of usage of the crypto object, which means that the authentication object is not stored in the Keystore, so it does not require a valid fingerprint to unlock the application. Another method involves bypassing the insecure usage of the crypto object, which means that if the key is not used to decrypt any data in the application, the authentication can be bypassed.
The authentication implementation relies on the callback onAuthenticationSucceded being called. The researchers from F-Secure developed a Frida script that can be used to bypass the NULL CryptoObject in onAuthenticationSucceeded(…). The script will automatically bypass the fingerprint when the aforementioned method is called. Here is a short example that shows the bypass for the Android Fingerprint. The complete application can be downloaded from my GitHub.
biometricPrompt = new BiometricPrompt(this, executor, new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { Toast.makeText(MainActivity.this,"Success",Toast.LENGTH_LONG).show(); } });
frida -U -f com.st3v3nss.insecurebankingfingerprint --no-pause -l fingerprint-bypass.js
This Frida script developed by F-Secure can be used to bypass the insecure usage of the crypto object. All the script needs to do is manually call the onAuthenticationSucceded with a non-authorized (not unlocked by fingerprint) CryptoObject stored in the Keystore. The catch is if the application will attempt to use another cipher object, then an exception will be thrown. This script will attempt to call onAuthenticationSucceded and catch the exception javax.crypto.IllegalBlockSizeException in Cipher class. From now on, any objects the application uses will be encrypted using this new key.
$ frida -U -f com.st3v3nss.insecurebankingfingerprint --no-pause -l fingerprint-bypass-via-exception-handling.js
Now, go to the fingerprint screen and wait for the authenticate() to be called. Once you see that on the screen, go ahead and type bypass() in the Frida console:
Spawning `com.st3v3nss.insecurebankingfingerprint`... [Android Emulator 5554::com.st3v3nss.insecurebankingfingerprint ]-> Hooking BiometricPrompt.authenticate()... Hooking BiometricPrompt.authenticate2()... Hooking FingerprintManager.authenticate()... [Android Emulator 5554::com.st3v3nss.insecurebankingfingerprint ]-> bypass()
As we finished exploiting the Android Biometric Authentication issues, we can shift our attention on the iOS environment. During a local authentication, an app authenticates the user against credentials stored locally on the device. In other words, the user “unlocks” the app or some inner layer of functionality by providing a set of valid biometric characteristics such as face or fingerprint, which is verified by referencing local data.
Shortly, the Local Authentication framework provides facilities for requesting Touch ID authentication from users. Developers can display and use an authentication prompt using the function evaluatePolicy of the LAContext class. The main drawback of this approach is that the function returns a boolean value, not a cryptographic object that can be further used to decrypt sensitive data stored inside the Keychain.
OWASP provided detailed information regarding the Local Authentication and how it is done on the iOS environment which can be found here so it would be pointless to try to reinvent the wheel.
For our testing purposes, let’s use the DVIA-v2 application (Damn Vulnerable iOS App), as it already implements the TouchID issue, described in the following code snippet.
+(void)authenticateWithTouchID { LAContext *myContext = [[LAContext alloc] init]; NSError *authError = nil; NSString *myLocalizedReasonString = @"Please authenticate yourself"; if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) { [myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:myLocalizedReasonString reply:^(BOOL success, NSError *error) { if (success) { dispatch_async(dispatch_get_main_queue(), ^{ [TouchIDAuthentication showAlert:@"Authentication Successful" withTitle:@"Success"]; }); } else { dispatch_async(dispatch_get_main_queue(), ^{ [TouchIDAuthentication showAlert:@"Authentication Failed !" withTitle:@"Error"]; }); } }]; } else { dispatch_async(dispatch_get_main_queue(), ^{ [TouchIDAuthentication showAlert:@"Your device doesn't support Touch ID or you haven't configured Touch ID authentication on your device" withTitle:@"Error"]; }); } }
As already mentioned, Frida is your way-to-go tool when it comes to mobile pentesting, regardless of the device you are using.
Now, to be able to bypass the Local Authentication, we have to write a Frida script that bypasses the aforementioned evaluatePolicy check. As you can see in the above-pasted code snippet, the evaluatePolicy uses a callback that determines the result. So, the easiest way to achieve the hack is to intercept that callback and make sure it always returns the success=1.
if(ObjC.available) { console.log("Injecting..."); var hook = ObjC.classes.LAContext["- evaluatePolicy:localizedReason:reply:"]; Interceptor.attach(hook.implementation, { onEnter: function(args) { var block = new ObjC.Block(args[4]); const callback = block.implementation; block.implementation = function (error, value) { console.log("Changing the result value to true") const result = callback(1, null); return result; }; }, }); } else { console.log("Objective-C Runtime is not available!"); }
$ frida -U -f com.highaltitudehacks.DVIAswiftv2 --no-pause -l fingerprint-bypass-ios.js
Another valid method used to bypass the iOS Biometric Local Authentication is to use objection and its pre-build script. Firstly, attach the object to the target application.
$ objection --gadget DVIA-v2 explore
Now use the pre-built Objection script for fingerprint bypasses.
$ ios ui biometrics_bypass
The objection script is created in such a way that it would work on both Swift and Objective-C. Now click on any of the fingerprints on the screen for Swift or Objective-C. It will prompt you to put your fingerprint. Put the wrong finger on the sensor (you need to have it set up on your device before you do this). When the wrong fingerprint was used, the app will alert you that an error occurred. In this case, just hit the cancel button instead. Now you will see a new popup that tells us that the fingerprint was successful!
Although it is quite difficult to exploit this issue as it requires you to know the device PIN code for adding/changing fingerprints in the device settings, you can still report this vulnerability in your pentest reports. The risk of not invalidating the fingerprint and make the user redo the fingerprint onboarding means that if someone could add a new fingerprint, he can access the application without any restrictions (the worse it gets when the testing app is a banking one).
After you enrolled the new fingerprint, just use it to unlock the application and see if it accepts or not the newly added biometric.
To summarize, both iOS and Android implement biometric authentication, but both come with their drawbacks. A misconfigured Fingerprint/Face ID authentication exposes the application to threats that might allow attackers to perform, in the worst-case, account takeovers actions.
To implement secure biometric authentication, developers must use the Keychain/Keystore to access objects only when a valid biometric is used (you can’t unlock the Keychain/Keystore with an invalid biometric).
https://philkeeble.com/ios/iOS-Bypass-Fingerprint/
https://github.com/prateek147/DVIA-v2
https://github.com/FSecureLABS/android-keystore-audit/tree/master/frida-scripts
https://medium.com/@ashishf6/exploiting-android-fingerprint-authentication-25dd9263bd74
https://medium.com/securing/bypassing-your-apps-biometric-checks-on-ios-c2555c81a2dc