Security Alert: Frida Bypass On Rooted Devices
Hey guys! Today, we're diving into a critical security vulnerability that affects the verifyIdentity() function in the @capgo/capacitor-native-biometric plugin. If you're using this plugin in your Capacitor or Ionic apps, especially on Android, you'll want to pay close attention. This issue allows attackers with rooted devices and Frida to bypass biometric authentication, potentially compromising your app's security. Let's break down the problem, its impact, and what you can do about it.
Understanding the Vulnerability
The core of the issue lies in how the verifyIdentity() function is used and trusted. The function, part of the @capgo/capacitor-native-biometric plugin, is designed to verify a user's identity using biometric authentication. The problem arises when developers treat the boolean result of this function as the sole gatekeeper for authentication. On rooted devices, attackers can use tools like Frida to hook into the function and force it to return true, effectively bypassing biometric checks.
Technical Deep Dive
At a high level, the vulnerability can be exploited as follows:
- Installation on a Rooted Device: The app, using
@capgo/capacitor-native-biometricv6.0.4, is installed on a rooted Android device with the Frida server running. - Credential Enrollment: The app enrolls credentials using
NativeBiometric.setCredentials(). This typically involves setting a username, password, and server identifier.
await NativeBiometric.setCredentials({
username: 'user-id',
password: '123456',
server: 'app-pin'
});
- Authentication Attempt: The app calls
NativeBiometric.verifyIdentity()to authenticate the user.
const ok = await NativeBiometric.verifyIdentity();
if (ok) unlock();
- Frida Hooking: An attacker uses Frida to hook either the JavaScript/native call or the
onAuthenticationSucceededcallback. This allows them to forceverifyIdentity()to returntruewithout requiring actual biometric input.
The Role of Frida
For those new to the tool, Frida is a dynamic instrumentation toolkit. Think of it as a super powerful debugger that lets you inject JavaScript snippets into running processes. Attackers love it because it allows them to peek under the hood of an application, modify its behavior on the fly, and even bypass security checks. In this case, Frida is used to intercept the verifyIdentity() call and manipulate its return value.
Why is this happening?
The root cause is that biometric verification, when solely relied upon, becomes a UI gate rather than a robust security mechanism. The verifyIdentity() function, as it stands, doesn't tie the biometric authentication directly to a cryptographic operation. This means that the app receives a simple boolean (true or false) indicating success or failure, which is susceptible to manipulation.
Impact of the Vulnerability
This vulnerability has a high impact, especially for apps that heavily rely on verifyIdentity() as the primary authentication method. Imagine apps that use biometric authentication to protect local PINs, vaults, or other sensitive flows. On rooted or tampered devices, an attacker could easily bypass these protections, gaining unauthorized access. This is a significant risk, and it's crucial to address it promptly.
Real-World Scenarios
Consider a few scenarios where this vulnerability could be exploited:
- Banking Apps: An attacker could bypass biometric login to access a user's bank account.
- Password Managers: An attacker could unlock a password manager, gaining access to all stored credentials.
- Healthcare Apps: An attacker could access sensitive patient data.
- Enterprise Apps: An attacker could bypass security measures to access confidential company information.
As you can see, the potential consequences are severe, highlighting the need for a more secure approach to biometric authentication.
Recommendations and Solutions
So, what can we do to mitigate this risk? Here are a few actionable recommendations to enhance the security of your apps:
1. Documentation Update
First and foremost, the plugin documentation needs a clear security note. This note should explicitly state that relying solely on verifyIdentity() is unsafe and that it can be bypassed on rooted devices. It should emphasize that verifyIdentity() acts as a UI gate and not a foolproof security measure. Provide a recommended secure pattern for developers to follow.
2. API Enhancement (The Preferred Approach)
The ideal solution is to introduce an atomic native method that performs cryptographic verification entirely within the native layer. This method should tie biometric authentication to keystore-backed cryptography, ensuring that plaintext data never reaches JavaScript. Here’s an example of how such a method might work:
-
authenticateAndCompare({ server, candidateHash })This method would operate as follows:
- Native Layer: Run BiometricPrompt with CryptoObject (keystore-backed).
- Decryption/Signing: Perform decryption or signing operations within the native layer.
- Comparison: Compare the result to
candidateHash. - Return Value: Return a boolean
{ verified: boolean }indicating whether the verification was successful.
By handling the cryptographic operations natively, we eliminate the risk of manipulation in the JavaScript layer. This approach ensures that the biometric authentication is directly linked to a secure cryptographic operation, making it much harder to bypass.
3. Secure Code Examples
Provide developers with short, secure code examples in the README. These examples should demonstrate the correct way to enroll credentials (using setCredentials) and perform native verification and comparison. If the atomic native method (as described above) isn't immediately available, offer a recommended alternative pattern that developers can implement in the meantime.
Example Secure Pattern (Interim Solution)
While waiting for the API enhancement, consider this interim approach:
- Enrollment: Generate a cryptographic key and store it securely in the Android Keystore or iOS Keychain during the
setCredentialsphase. - Authentication:
- Use
verifyIdentity()to trigger the biometric prompt. - If
verifyIdentity()returnstrue, use the stored key to perform a cryptographic operation (e.g., signing a challenge). - Verify the result on the server or within a secure part of the app.
- Use
This pattern adds an extra layer of security by tying the biometric authentication to a cryptographic operation, making it significantly harder to bypass.
4. Implement Root Detection
While not a complete solution, implementing root detection can add another layer of defense. If your app detects that it's running on a rooted device, you can take additional security measures, such as:
- Disabling certain features.
- Showing a warning message to the user.
- Enforcing stricter security policies.
However, keep in mind that root detection can be bypassed by sophisticated attackers, so it shouldn't be your only line of defense.
Final Thoughts
Security is a continuous journey, and staying ahead of potential vulnerabilities is crucial. The verifyIdentity() bypass on rooted devices highlights the importance of not solely relying on UI-level checks for authentication. By implementing the recommendations discussed, especially the atomic native method for cryptographic verification, we can significantly enhance the security of our apps.
Remember, guys, it's always better to be proactive when it comes to security. Keep learning, stay vigilant, and let's build more secure apps together!