Skip to content

Commit a944443

Browse files
committed
Merge branch 'main' into redsun82/bzlmod
2 parents c0eeb7a + 3d9f9af commit a944443

File tree

58 files changed

+1071
-406
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1071
-406
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* An extension point for sanitizers of the query `java/unvalidated-url-redirection` has been added.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/** Definitions for the insecure local authentication query. */
2+
3+
import java
4+
5+
/** A base class that is used as a callback for biometric authentication. */
6+
private class AuthenticationCallbackClass extends Class {
7+
AuthenticationCallbackClass() {
8+
this.hasQualifiedName("android.hardware.fingerprint",
9+
"FingerprintManager$AuthenticationCallback")
10+
or
11+
this.hasQualifiedName("android.hardware.biometrics", "BiometricPrompt$AuthenticationCallback")
12+
or
13+
this.hasQualifiedName("androidx.biometric", "BiometricPrompt$AuthenticationCallback")
14+
}
15+
}
16+
17+
/** An implementation of the `onAuthenticationSucceeded` method for an authentication callback. */
18+
class AuthenticationSuccessCallback extends Method {
19+
AuthenticationSuccessCallback() {
20+
this.getDeclaringType().getASupertype+() instanceof AuthenticationCallbackClass and
21+
this.hasName("onAuthenticationSucceeded")
22+
}
23+
24+
/** Gets the parameter containing the `authenticationResult`. */
25+
Parameter getResultParameter() { result = this.getParameter(0) }
26+
27+
/** Gets a use of the result parameter that's used in a `super` call to the base `AuthenticationCallback` class. */
28+
private VarAccess getASuperResultUse() {
29+
exists(SuperMethodCall sup |
30+
sup.getEnclosingCallable() = this and
31+
result = sup.getArgument(0) and
32+
result = this.getResultParameter().getAnAccess() and
33+
this.getDeclaringType().getASupertype() instanceof AuthenticationCallbackClass
34+
)
35+
}
36+
37+
/** Gets a use of the result parameter, other than one used in a `super` call. */
38+
VarAccess getAResultUse() {
39+
result = this.getResultParameter().getAnAccess() and
40+
not result = this.getASuperResultUse()
41+
}
42+
}

java/ql/lib/semmle/code/java/security/UrlRedirect.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ private import semmle.code.java.dataflow.ExternalFlow
66
import semmle.code.java.frameworks.Servlets
77
import semmle.code.java.frameworks.ApacheHttp
88
private import semmle.code.java.frameworks.JaxWS
9+
private import semmle.code.java.security.RequestForgery
910

1011
/** A URL redirection sink. */
1112
abstract class UrlRedirectSink extends DataFlow::Node { }
1213

14+
/** A URL redirection sanitizer. */
15+
abstract class UrlRedirectSanitizer extends DataFlow::Node { }
16+
1317
/** A default sink represeting methods susceptible to URL redirection attacks. */
1418
private class DefaultUrlRedirectSink extends UrlRedirectSink {
1519
DefaultUrlRedirectSink() { sinkNode(this, "url-redirection") }
@@ -42,3 +46,6 @@ private class ApacheUrlRedirectSink extends UrlRedirectSink {
4246
)
4347
}
4448
}
49+
50+
private class DefaultUrlRedirectSanitizer extends UrlRedirectSanitizer instanceof RequestForgerySanitizer
51+
{ }

java/ql/lib/semmle/code/java/security/UrlRedirectQuery.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module UrlRedirectConfig implements DataFlow::ConfigSig {
1111
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
1212

1313
predicate isSink(DataFlow::Node sink) { sink instanceof UrlRedirectSink }
14+
15+
predicate isBarrier(DataFlow::Node node) { node instanceof UrlRedirectSanitizer }
1416
}
1517

1618
/**

java/ql/src/Security/CWE/CWE-078/ExecRelative.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* malicious changes in the PATH environment variable.
55
* @kind problem
66
* @problem.severity warning
7-
* @security-severity 9.8
7+
* @security-severity 5.4
88
* @precision medium
99
* @id java/relative-path-command
1010
* @tags security
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Biometric local authentication such as fingerprint recognition can be used to protect sensitive data or actions within an application.
9+
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.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
Generate a secure key in the Android <code>KeyStore</code>. Ensure that the <code>onAuthenticationSuccess</code> callback for a biometric prompt uses it
16+
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.
17+
</p>
18+
</recommendation>
19+
20+
<example>
21+
<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>
22+
<sample src="AndroidInsecureLocalAuthenticationBad.java" />
23+
<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>
24+
<sample src="AndroidInsecureLocalAuthenticationGood.java" />
25+
</example>
26+
27+
<references>
28+
<li>
29+
OWASP Mobile Application Security: <a href="https://mas.owasp.org/MASTG/Android/0x05f-Testing-Local-Authentication/">Android Local Authentication</a>
30+
</li>
31+
<li>
32+
OWASP Mobile Application Security: <a href="https://mas.owasp.org/MASTG/tests/android/MASVS-AUTH/MASTG-TEST-0018/">Testing Biometric Authentication</a>
33+
</li>
34+
<li>
35+
WithSecure: <a href="https://labs.withsecure.com/publications/how-secure-is-your-android-keystore-authentication">How Secure is your Android Keystore Authentication?</a>
36+
</li>
37+
<li>
38+
Android Developers: <a href="https://developer.android.com/training/sign-in/biometric-auth">Biometric Authentication</a>
39+
</li>
40+
41+
</references>
42+
</qhelp>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @name Insecure local authentication
3+
* @description Local authentication that does not make use of a `CryptoObject` can be bypassed.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @security-severity 4.4
7+
* @precision high
8+
* @id java/android/insecure-local-authentication
9+
* @tags security
10+
* external/cwe/cwe-287
11+
*/
12+
13+
import java
14+
import semmle.code.java.security.AndroidLocalAuthQuery
15+
16+
from AuthenticationSuccessCallback c
17+
where not exists(c.getAResultUse())
18+
select c, "This authentication callback does not use its result for a cryptographic operation."
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
biometricPrompt.authenticate(
2+
cancellationSignal,
3+
executor,
4+
new BiometricPrompt.AuthenticationCallback {
5+
@Override
6+
// BAD: This authentication callback does not make use of a `CryptoObject` from the `result`.
7+
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
8+
grantAccess()
9+
}
10+
}
11+
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
private void generateSecretKey() {
2+
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
3+
"MySecretKey",
4+
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
5+
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
6+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
7+
.setUserAuthenticationRequired(true)
8+
.setInvalidatedByBiometricEnrollment(true)
9+
.build();
10+
KeyGenerator keyGenerator = KeyGenerator.getInstance(
11+
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
12+
keyGenerator.init(keyGenParameterSpec);
13+
keyGenerator.generateKey();
14+
}
15+
16+
17+
private SecretKey getSecretKey() {
18+
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
19+
keyStore.load(null);
20+
return ((SecretKey)keyStore.getKey("MySecretKey", null));
21+
}
22+
23+
private Cipher getCipher() {
24+
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
25+
+ KeyProperties.BLOCK_MODE_CBC + "/"
26+
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
27+
}
28+
29+
public prompt(byte[] encryptedData) {
30+
Cipher cipher = getCipher();
31+
SecretKey secretKey = getSecretKey();
32+
cipher.init(Cipher.DECRYPT_MODE, secretKey);
33+
34+
biometricPrompt.authenticate(
35+
new BiometricPrompt.CryptoObject(cipher),
36+
cancellationSignal,
37+
executor,
38+
new BiometricPrompt.AuthenticationCallback() {
39+
@Override
40+
// GOOD: This authentication callback uses the result to decrypt some data.
41+
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
42+
Cipher cipher = result.getCryptoObject().getCipher();
43+
byte[] decryptedData = cipher.doFinal(encryptedData);
44+
grantAccessWithData(decryptedData);
45+
}
46+
}
47+
);
48+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
---
3+
category: newQuery
4+
---
5+
* Added a new query `java/android/insecure-local-authentication` for finding uses of biometric authentication APIs that do not make use of a `KeyStore`-backed key and thus may be bypassed.

0 commit comments

Comments
 (0)