コンテンツにスキップ

Biometric Authentication Bypass

生体認証のバイパス

説明

モバイル生体認証の安全な実装は、アプリケーションの機密データにアクセスするために Face ID または Touch ID 認証を使用する必要性を保証します。このような安全な実装は、ログイン時に指紋や顔を検証するだけにとどまりません。これには、生体データを使用してアプリケーションの機密データを暗号化することも含まれます。

この暗号化により保護のレイヤーが追加され、許可されていない個人が機密情報にアクセスしたり使用したりすることが極めて困難になります。マルウェアや物理的アクセスによって許可されていない第三者がデバイスにアクセスした場合、生体データを用いた暗号化が重要になります。

Android

Android は、機密情報を保護するために生体認証を強制するメカニズムを提供しています。生体認証は時間の経過とともに進化し、ユーザーエクスペリエンス、開発者エクスペリエンス、およびセキュリティの向上を提供してきました。

FingerprintManager を使用した以前の実装は非推奨であり、使用してはなりません。適切な実装では、BiometricPrompt および CryptoObject とともに BiometricManager を使用する必要があります。

CryptoObject は、暗号化、復号、および署名検証のための暗号プリミティブを提供します。

以下の例では、cryptoObject なしで authenticate メソッドを呼び出しているため、認証をバイパスされる脆弱性があります。

fun showBiometricPrompt(
    title: String = "Biometric Authentication",
    subtitle: String = "Enter biometric credentials to proceed.",
    description: String = "Input your Fingerprint or FaceID to ensure it's you!",
    activity: AppCompatActivity,
    listener: BiometricAuthListener,
    cryptoObject: BiometricPrompt.CryptoObject? = null,
    allowDeviceCredential: Boolean = false
) {
  // 1
  val promptInfo = setBiometricPromptInfo(
      title,
      subtitle,
      description,
      allowDeviceCredential
  )

  // 2
  val biometricPrompt = initBiometricPrompt(activity, listener)

  // 3
  biometricPrompt.apply {
    if (cryptoObject == null) authenticate(promptInfo)
    else authenticate(promptInfo, cryptoObject)
  }
}

iOS

Local Authentication フレームワークを使用すると、開発者はユーザーに Touch ID 認証を要求できます。このプロセスを開始するために、開発者は LAContext クラス内の evaluatePolicy 関数を使用して認証プロンプトを呼び出すことができます。ただし、このアプローチは安全ではないことに注意することが重要です。関数は、Keychain 内に保存された機密データを復号するために利用できる暗号化オブジェクトを提供するのではなく、ブール値を返します。

暗号化オブジェクトがないと、攻撃者はメモリを操作して生体認証のチェックをバイパスし、アプリケーションへのログインを成功させる可能性があります。ただし、生体データで暗号化が使用されている場合、攻撃者はアプリケーションデータを解釈したり利用したりすることはできません。これにより、アプリケーションの機密情報の機密性が維持され、ユーザーのデータのプライバシーとセキュリティが保護されます。

DVIA の以下の例では、frida を使用して evaluatePolicy をフックすることで、生体認証をバイパスすることが可能です。

+(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"];
        });
    }
}

推奨事項

Kotlin

ネイティブ Android の場合、CryptoObject を使用して生体認証を実装します。

CryptoObject を使用する場合の認証フローは次のようになります。

  • アプリは KeyStore にキーを作成します。パラメータは次のとおりです。
  • setUserAuthenticationRequiredtrue に設定します。
  • setInvalidatedByBiometricEnrollmenttrue に設定します。
  • setUserAuthenticationValidityDurationSeconds-1 に設定します。
val paramsBuilder = KeyGenParameterSpec.Builder(keyName, KeyProperties.PURPOSE_SIGN)
        paramsBuilder.apply {
            when {
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
                    setDigests(KeyProperties.DIGEST_SHA256)
                    setUserAuthenticationRequired(true)
                    setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) // ECDSA parameter (P-256) curve
                    setInvalidatedByBiometricEnrollment(true)
                    setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
                }
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q -> {
                    setDigests(KeyProperties.DIGEST_SHA256)
                    setUserAuthenticationRequired(true)
                    setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
                    setInvalidatedByBiometricEnrollment(true)
                    setUserAuthenticationValidityDurationSeconds(-1)
                }
                else -> {
                    setDigests(KeyProperties.DIGEST_SHA256)
                    setUserAuthenticationRequired(true)
                    setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
                    setUserAuthenticationValidityDurationSeconds(-1)
                }
            }
        }
  • KeyStore キーは、セッション情報や認証トークンなど、ユーザーを認証する情報を暗号化するために使用する必要があります。

  • データを復号するために KeyStore からキーがアクセスされる前に、生体情報が提示されます。生体情報は、authenticate メソッドと CryptoObject を使用して検証されます。生体認証が成功した後にのみ KeyStore キーを使用できるため、ルート化されたデバイスであっても、このソリューションをバイパスすることはできません。

fun showBiometricPrompt(
        title: String = "Biometric Authentication",
        subtitle: String = "Enter biometric credentials to proceed.",
        description: String = "Input your Fingerprint or FaceID
to ensure it's you!",
        activity: AppCompatActivity,
        listener: BiometricAuthListener
    ) {
        val promptInfo = setBiometricPromptInfo(title, subtitle, description)

        val biometricPrompt = initBiometricPrompt(activity, listener)

        biometricPrompt.authenticate(
            promptInfo, BiometricPrompt.CryptoObject(
                CryptoUtil.getOrCreateSignature()
            )
        )
    }
  • CryptoObject が authenticate メソッドの一部として使用されていない場合、デバッガーや Frida などのツールを使用した動的インストルメンテーションによってバイパスされる可能性があります。

Swift

Keychain を使用して secretKey を保存し、Keychain からアイテムにアクセスするために生体認証の使用を強制します。

1- 生体認証で保護された Keychain アイテムを作成します。

SecAccessControlCreateWithFlags を使用して、次のパラメータで SecAccessControl を作成します。

  • kSecAttrAccessibleWhenUnlockedThisDeviceOnly: iOS デバイスがロック解除されている場合にのみ、Keychain エントリを読み取ることができます。また、iCloud 経由で他のデバイスにコピーされることはなく、バックアップにも追加されません。
  • .biometryCurrentSet: Touch ID または Face ID 認証の要件を設定します。これにより、エントリは現在登録されている生体データに厳密に結び付けられます。
static func getBioSecAccessControl() -> SecAccessControl {
       var access: SecAccessControl?
       var error: Unmanaged<CFError>?
           access = SecAccessControlCreateWithFlags(nil,
               kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
               .biometryCurrentSet,
               &error)
       precondition(access != nil, "SecAccessControlCreateWithFlags failed")
       return access!
   }


static func createBioProtectedEntry(key: String, data: Data) -> OSStatus {
       let query = [
           kSecClass as String: kSecClassGenericPassword as String,
           kSecAttrAccount as String: key,
           kSecAttrAccessControl as String: getBioSecAccessControl(),
           kSecValueData as String: data ] as CFDictionary
       return SecItemAdd(query as CFDictionary, nil)
   }

2- 生体認証で保護されたエントリを読み取ります。

static func loadBioProtected(key: String, context: LAContext? = nil,
                                prompt: String? = nil) -> Data? {

    var query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecReturnData as String: kCFBooleanTrue,
            kSecAttrAccessControl as String: getBioSecAccessControl(),
            kSecMatchLimit as String: kSecMatchLimitOne ]

    if let context = context {
        query[kSecUseAuthenticationContext as String] = context
        query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUISkip
    }

    if let prompt = prompt {
        query[kSecUseOperationPrompt as String] = prompt
    }

    var dataTypeRef: AnyObject? = nil
    let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

    if status == noErr {
        return (dataTypeRef! as! Data)
    } else {
        return nil
    }
}

static func redBioProtectedEntry(entryName: String) {
    let authContext = LAContext()
    let accessControl = SecAccessControlCreateWithFlags(nil,
                kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                .biometryCurrentSet,
                &error)
    authContext.evaluateAccessControl(accessControl, operation: .useItem, localizedReason: "Access sample keychain entry") {
        (success, error) in
        var result = ""
        if success, let data = loadBioProtected(key: entryName, context: authContext) {
            let result = String(decoding: data, as: UTF8.self)
        } else {
            result = "Can't read entry, error: \(error?.localizedDescription ?? "-")"
        }
    }
}

Flutter

Flutter (Android および iOS の両方) の場合、biometric_storage は、生体認証を使用して暗号化されたデータをデバイスに書き込み、および読み取ることを可能にするプラグインです。

内部の実装ではベストプラクティスが適用されており、適切な SecAccessControlCreateFlags を持つ SecAccessControl を使用して、Touch ID または Face ID によるアクセスを制限します。

  • 最初のステップは、生体認証後にデータの書き込みと読み取りを行うためのアクセスオブジェクトを作成することです。

```dart /// Retrieves the given biometric storage file. Each store is completely separated and has its own encryption and biometric lock.

Future _getStorageFile() async { final authStorage = await BiometricStorage().getStorage('authenticated_storage',options:StorageFileInitOptions( ///Always call it with authenticationRequired=trueandauthenticationValidityDurationSeconds = -1 to ensure the secure implementation of bioùetric authentication. authenticationValidityDurationSeconds: -1, authenticationRequired: true, androidBiometricOnly: true, )); return authStorage; } ```

  • 安全なストレージにデータを書き込みます。

```dart /// Retrieves the given biometric storage file. Each store is completely separated and has its own encryption and biometric lock.

Future createBioProtectedEntry(context) async { if (await _checkAuthenticate() == false) { showAlertDialog(context,const Text("Can't use biometric auth on this device.")); return ; } _storageFile = await _getStorageFile(); await _storageFile?.write(_my_secret_data); } ```

  • データを読み取ります。

```dart Future redBioProtectedEntry(context) async { if (await _checkAuthenticate() == false) { showAlertDialog(context,const Text("Can't use biometric auth on this device.")); return ; } if (_storageFile == null){ showAlertDialog(context,const Text("Enable authentication first.")); return ; } final data = await _storageFile?.read(); showAlertDialog(context,Text(data!));

} ```

リンク

標準

  • OWASP_MASVS_L2:
    • MSTG_AUTH_8
  • GDPR:
    • ART_5
    • ART_32
  • PCI_STANDARDS:
    • REQ_6_2
    • REQ_6_3
    • REQ_8_3
  • OWASP_MASVS_v2_1:
    • MASVS_AUTH_2
  • HIPAA_CONTROLS:
    • SECURITY221
    • SECURITY212
    • SECURITY213
    • SECURITY251
  • SOC2_CONTROLS:
    • CC_2_1
    • CC_4_1
    • CC_6_7
    • CC_7_1
    • CC_7_2
    • CC_7_4
    • CC_7_5
  • CNIL_FOR_DEVELOPERS:
    • DEVELOPERS_4_1_2