Biometric Authentication Bypass
生物特征认证绕过
描述
移动生物特征认证的安全实现保证了需要使用 Face ID 或 Touch ID 认证才能访问应用程序的敏感数据。这种安全的实现不仅仅是验证指纹或面部来进行登录,还包括使用生物特征数据加密应用程序的敏感数据。
这种加密增加了一层额外的保护,使得未经授权的个人极难访问或使用敏感信息。如果未经授权的一方通过恶意软件或物理访问获得了对设备的访问权限,使用生物特征数据进行加密就变得至关重要。
Android
Android 提供了强制执行生物特征认证以保护敏感信息的机制。随着时间的推移,生物特征认证不断发展,以提供更好的用户体验、开发者体验和更高的安全性。
以前使用 FingerprintManager 的实现已被弃用,并且不得再使用。正确的实现必须使用 BiometricManager 结合 BiometricPrompt 和 CryptoObject。
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 钩取(hooking)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 中创建一个密钥,并进行如下设置:
- 将
setUserAuthenticationRequired设置为true - 将
setInvalidatedByBiometricEnrollment设置为true - 将
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 中的密钥只有在成功的生物特征认证后才能使用,因此即使在已越狱(rooted)的设备上,该解决方案也无法被绕过。
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 等工具进行动态插桩(dynamic instrumentation)来绕过认证。
Swift
使用 Keychain 来存储 secretKey,并强制要求使用生物特征认证才能从 Keychain 访问该项。
1- 创建受生物特征保护的 keychain 项:
使用 SecAccessControlCreateWithFlags 以如下参数创建一个 SecAccessControl:
- kSecAttrAccessibleWhenUnlockedThisDeviceOnly: keychain 条目仅在 iOS 设备解锁时才可被读取。此外,它不会通过 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.
FutureauthenticationRequired=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
- 读取数据:
```dart
Future
} ```
链接
- Secure Mobile Biometric Authentication
- Bypass Biometric Authentication
- Using BiometricPrompt with CryptoObject: How and Why
- Android Biometric API: Getting Started
- MOBILE PENTESTING 101 – BYPASSING BIOMETRIC AUTHENTICATION
标准
- 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