Merge pull request #15481 from joefarebrother/android-local-auth

Java: Add query for insecure local authentication
This commit is contained in:
Joe Farebrother
2024-02-12 13:48:53 +00:00
committed by GitHub
21 changed files with 624 additions and 18 deletions

View File

@@ -0,0 +1,42 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Biometric local authentication such as fingerprint recognition can be used to protect sensitive data or actions within an application.
However, if this authentication does not use a <code>KeyStore</code>-backed key, it can be bypassed by a privileged malicious application, or by an attacker with physical access using application hooking tools such as Frida.
</p>
</overview>
<recommendation>
<p>
Generate a secure key in the Android <code>KeyStore</code>. Ensure that the <code>onAuthenticationSuccess</code> callback for a biometric prompt uses it
in a way that is required for the sensitive parts of the application to function, such as by using it to decrypt sensitive data or credentials.
</p>
</recommendation>
<example>
<p>In the following (bad) case, no <code>CryptoObject</code> is required for the biometric prompt to grant access, so it can be bypassed.</p>
<sample src="AndroidInsecureLocalAuthenticationBad.java" />
<p>In the following (good) case, a secret key is generated in the Android <code>KeyStore</code>. The application requires this secret key for access, using it to decrypt data.</p>
<sample src="AndroidInsecureLocalAuthenticationGood.java" />
</example>
<references>
<li>
OWASP Mobile Application Security: <a href="https://mas.owasp.org/MASTG/Android/0x05f-Testing-Local-Authentication/">Android Local Authentication</a>
</li>
<li>
OWASP Mobile Application Security: <a href="https://mas.owasp.org/MASTG/tests/android/MASVS-AUTH/MASTG-TEST-0018/">Testing Biometric Authentication</a>
</li>
<li>
WithSecure: <a href="https://labs.withsecure.com/publications/how-secure-is-your-android-keystore-authentication">How Secure is your Android Keystore Authentication?</a>
</li>
<li>
Android Developers: <a href="https://developer.android.com/training/sign-in/biometric-auth">Biometric Authentication</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,18 @@
/**
* @name Insecure local authentication
* @description Local authentication that does not make use of a `CryptoObject` can be bypassed.
* @kind problem
* @problem.severity warning
* @security-severity 4.4
* @precision high
* @id java/android/insecure-local-authentication
* @tags security
* external/cwe/cwe-287
*/
import java
import semmle.code.java.security.AndroidLocalAuthQuery
from AuthenticationSuccessCallback c
where not exists(c.getAResultUse())
select c, "This authentication callback does not use its result for a cryptographic operation."

View File

@@ -0,0 +1,11 @@
biometricPrompt.authenticate(
cancellationSignal,
executor,
new BiometricPrompt.AuthenticationCallback {
@Override
// BAD: This authentication callback does not make use of a `CryptoObject` from the `result`.
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
grantAccess()
}
}
)

View File

@@ -0,0 +1,48 @@
private void generateSecretKey() {
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
"MySecretKey",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
.build();
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(keyGenParameterSpec);
keyGenerator.generateKey();
}
private SecretKey getSecretKey() {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
return ((SecretKey)keyStore.getKey("MySecretKey", null));
}
private Cipher getCipher() {
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
}
public prompt(byte[] encryptedData) {
Cipher cipher = getCipher();
SecretKey secretKey = getSecretKey();
cipher.init(Cipher.DECRYPT_MODE, secretKey);
biometricPrompt.authenticate(
new BiometricPrompt.CryptoObject(cipher),
cancellationSignal,
executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
// GOOD: This authentication callback uses the result to decrypt some data.
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
Cipher cipher = result.getCryptoObject().getCipher();
byte[] decryptedData = cipher.doFinal(encryptedData);
grantAccessWithData(decryptedData);
}
}
);
}