Skip to content

Commit 55dc783

Browse files
committed
Move from experimental and refactor
1 parent d912a98 commit 55dc783

13 files changed

+262
-287
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* The query "Cleartext storage of sensitive information using `SharedPreferences` on Android" (`java/android/cleartext-storage-shared-prefs`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @luchua-bc](https://github.com/github/codeql/pull/4675).
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/** Provides classes and predicates to reason about cleartext storage in Android's SharedPreferences. */
2+
3+
import java
4+
import semmle.code.java.dataflow.DataFlow
5+
import semmle.code.java.dataflow.DataFlow4
6+
import semmle.code.java.frameworks.android.SharedPreferences
7+
import semmle.code.java.security.CleartextStorageQuery
8+
9+
private class SharedPrefsCleartextStorageSink extends CleartextStorageSink {
10+
SharedPrefsCleartextStorageSink() {
11+
exists(MethodAccess m |
12+
m.getMethod() instanceof PutSharedPreferenceMethod and
13+
this.asExpr() = m.getArgument(1)
14+
)
15+
}
16+
}
17+
18+
/**
19+
* The call to get a `SharedPreferences.Editor` object, which can set shared preferences and be
20+
* stored to the device.
21+
*/
22+
class SharedPreferencesEditorMethodAccess extends Storable, MethodAccess {
23+
SharedPreferencesEditorMethodAccess() {
24+
this.getMethod() instanceof GetSharedPreferencesEditorMethod and
25+
not DataFlow::localExprFlow(any(MethodAccess ma |
26+
ma.getMethod() instanceof CreateEncryptedSharedPreferencesMethod
27+
), this.getQualifier())
28+
}
29+
30+
/** Gets an input, for example `password` in `editor.putString("password", password);`. */
31+
override Expr getAnInput() {
32+
exists(SharedPreferencesFlowConfig conf, DataFlow::Node editor |
33+
sharedPreferencesInput(editor, result) and
34+
conf.hasFlow(DataFlow::exprNode(this), editor)
35+
)
36+
}
37+
38+
/** Gets a store, for example `editor.commit();`. */
39+
override Expr getAStore() {
40+
exists(SharedPreferencesFlowConfig conf, DataFlow::Node editor |
41+
sharedPreferencesStore(editor, result) and
42+
conf.hasFlow(DataFlow::exprNode(this), editor)
43+
)
44+
}
45+
}
46+
47+
/**
48+
* Holds if `input` is not encrypted and is the second argument of a setter method
49+
* called on `editor`, which is an instance of `SharedPreferences$Editor`
50+
* .
51+
*/
52+
private predicate sharedPreferencesInput(DataFlow::Node editor, Expr input) {
53+
exists(MethodAccess m |
54+
m.getMethod() instanceof PutSharedPreferenceMethod and
55+
input = m.getArgument(1) and
56+
not exists(EncryptedValueFlowConfig conf | conf.hasFlow(_, DataFlow::exprNode(input))) and
57+
editor.asExpr() = m.getQualifier()
58+
)
59+
}
60+
61+
/**
62+
* Holds if `m` is a store method called on `editor`,
63+
* which is an instance of `SharedPreferences$Editor`.
64+
*/
65+
private predicate sharedPreferencesStore(DataFlow::Node editor, MethodAccess m) {
66+
m.getMethod() instanceof StoreSharedPreferenceMethod and
67+
editor.asExpr() = m.getQualifier()
68+
}
69+
70+
/** Flow from `SharedPreferences.Editor` to either a setter or a store method. */
71+
private class SharedPreferencesFlowConfig extends DataFlow::Configuration {
72+
SharedPreferencesFlowConfig() { this = "SharedPreferencesFlowConfig" }
73+
74+
override predicate isSource(DataFlow::Node src) {
75+
src.asExpr() instanceof SharedPreferencesEditorMethodAccess
76+
}
77+
78+
override predicate isSink(DataFlow::Node sink) {
79+
sharedPreferencesInput(sink, _) or
80+
sharedPreferencesStore(sink, _)
81+
}
82+
}
83+
84+
/**
85+
* Method call for encrypting sensitive information. As there are various implementations of
86+
* encryption (reversible and non-reversible) from both JDK and third parties, this class simply
87+
* checks method name to take a best guess to reduce false positives.
88+
*/
89+
private class EncryptedSensitiveMethodAccess extends MethodAccess {
90+
EncryptedSensitiveMethodAccess() {
91+
this.getMethod().getName().toLowerCase().matches(["%encrypt%", "%hash%"])
92+
}
93+
}
94+
95+
/** Flow configuration for encryption methods flowing to inputs of `SharedPreferences`. */
96+
private class EncryptedValueFlowConfig extends DataFlow4::Configuration {
97+
EncryptedValueFlowConfig() { this = "SensitiveStorage::EncryptedValueFlowConfig" }
98+
99+
override predicate isSource(DataFlow::Node src) {
100+
src.asExpr() instanceof EncryptedSensitiveMethodAccess
101+
}
102+
103+
override predicate isSink(DataFlow::Node sink) { sink instanceof SharedPrefsCleartextStorageSink }
104+
}

java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.java renamed to java/ql/src/Security/CWE/CWE-312/CleartextStorageSharedPrefs.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
public void testSetSharedPrefs(Context context, String name, String password)
22
{
33
{
4-
// BAD - save sensitive information in cleartext
4+
// BAD - sensitive information saved in cleartext.
55
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
66
Editor editor = sharedPrefs.edit();
77
editor.putString("name", name);
@@ -10,7 +10,7 @@ public void testSetSharedPrefs(Context context, String name, String password)
1010
}
1111

1212
{
13-
// GOOD - save sensitive information in encrypted format
13+
// GOOD - save sensitive information encrypted with a custom method.
1414
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
1515
Editor editor = sharedPrefs.edit();
1616
editor.putString("name", encrypt(name));
@@ -19,7 +19,7 @@ public void testSetSharedPrefs(Context context, String name, String password)
1919
}
2020

2121
{
22-
// GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx.
22+
// GOOD - sensitive information saved using the built-in `EncryptedSharedPreferences` class in androidx.
2323
MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
2424
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
2525
.build();
@@ -37,3 +37,12 @@ public void testSetSharedPrefs(Context context, String name, String password)
3737
editor.commit();
3838
}
3939
}
40+
41+
private static String encrypt(String cleartext) throws Exception {
42+
// Use an encryption or hashing algorithm in real world. The demo below just returns its
43+
// hash.
44+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
45+
byte[] hash = digest.digest(cleartext.getBytes(StandardCharsets.UTF_8));
46+
String encoded = Base64.getEncoder().encodeToString(hash);
47+
return encoded;
48+
}

java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.qhelp renamed to java/ql/src/Security/CWE/CWE-312/CleartextStorageSharedPrefs.qhelp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<qhelp>
33
<overview>
44
<p>
5-
<code>SharedPreferences</code> is an Android API that stores application preferences using simple sets of data values. Almost every Android application uses this API. It allows to easily save, alter, and retrieve the values stored in the user's profile. However, sensitive information should not be saved in cleartext. Otherwise it can be accessed by any process or user on rooted devices, or can be disclosed through chained vulnerabilities e.g. unexpected access to its private storage through exposed components.
5+
<code>SharedPreferences</code> is an Android API that stores application preferences using simple sets of data values. It allows to easily save, alter, and retrieve the values stored in the user's profile. However, sensitive information should not be saved in cleartext. Otherwise it can be accessed by any process or user in rooted devices, or can be disclosed through chained vulnerabilities, like unexpected access to the private storage through exposed components.
66
</p>
77
</overview>
88

@@ -24,10 +24,6 @@
2424
</example>
2525

2626
<references>
27-
<li>
28-
CWE:
29-
<a href="https://cwe.mitre.org/data/definitions/312.html">CWE-312: Cleartext Storage of Sensitive Information</a>
30-
</li>
3127
<li>
3228
Android Developers:
3329
<a href="https://developer.android.com/topic/security/data">Work with data more securely</a>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @name Cleartext storage of sensitive information using `SharedPreferences` on Android
3+
* @description Cleartext Storage of Sensitive Information using
4+
* SharedPreferences on Android allows access for users with root
5+
* privileges or unexpected exposure from chained vulnerabilities.
6+
* @kind problem
7+
* @problem.severity warning
8+
* @precision medium
9+
* @id java/android/cleartext-storage-shared-prefs
10+
* @tags security
11+
* external/cwe/cwe-312
12+
*/
13+
14+
import java
15+
import semmle.code.java.security.CleartextStorageSharedPrefsQuery
16+
17+
from SensitiveSource data, SharedPreferencesEditorMethodAccess s, Expr input, Expr store
18+
where
19+
input = s.getAnInput() and
20+
store = s.getAStore() and
21+
data.flowsToCached(input)
22+
select store, "'SharedPreferences' class $@ containing $@ is stored $@. Data was added $@.", s,
23+
s.toString(), data, "sensitive data", store, "here", input, "here"

java/ql/src/experimental/Security/CWE/CWE-312/CleartextStorageSharedPrefs.ql

Lines changed: 0 additions & 167 deletions
This file was deleted.

java/ql/test/experimental/query-tests/security/CWE-312/CleartextStorageSharedPrefs.expected

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)