Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Query for cleartext storage using Android SharedPreferences
  • Loading branch information
luchua-bc committed Nov 16, 2020
commit 0bd6255c41a79903219408aba71f9fe3335326ba
39 changes: 39 additions & 0 deletions java/ql/src/Security/CWE/CWE-312/ClearTextStorageSharedPrefs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
public void testSetSharedPrefs(Context context, String name, String password)
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
{
{
// BAD - save sensitive information in cleartext
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
editor.putString("name", name);
editor.putString("password", password);
editor.commit();
}

{
// GOOD - save sensitive information in encrypted format
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
editor.putString("name", encrypt(name));
editor.putString("password", encrypt(password));
editor.commit();
}

{
// GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx.
MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);

SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("name", name);
editor.putString("password", password);
editor.commit();
}
}
40 changes: 40 additions & 0 deletions java/ql/src/Security/CWE/CWE-312/ClearTextStorageSharedPrefs.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
<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 <code>SharedPreferences</code>. However, sensitive information shall 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.
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
</p>
</overview>

<recommendation>
<p>
Use the <code>EncryptedSharedPreferences</code> API or other encryption algorithms for storing sensitive information.
</p>
</recommendation>

<example>
<p>
In the first example, sensitive user information is stored in cleartext.
</p>

<p>
In the second and third examples, the code encrypts sensitive information before saving to the device.
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
</p>
<sample src="ClearTextStorageSharedPrefs.java" />
</example>

<references>
<li>
CWE:
<a href="https://cwe.mitre.org/data/definitions/312.html">CWE-312: Cleartext Storage of Sensitive Information</a>
</li>
<li>
Android Developers:
<a href="https://developer.android.com/topic/security/data">Work with data more securely</a>
</li>
<li>
PRO ANDROID DEV:
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
<a href="https://proandroiddev.com/encrypted-preferences-in-android-af57a89af7c8">Encrypted Preferences in Android</a>
</li>
</references>
</qhelp>
22 changes: 22 additions & 0 deletions java/ql/src/Security/CWE/CWE-312/ClearTextStorageSharedPrefs.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @name Cleartext storage of sensitive information using `SharedPreferences` on Android
* @description Cleartext Storage of Sensitive Information using SharedPreferences on Android allows user with root privileges to access or unexpected exposure from chained vulnerabilities.
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
* @kind problem
* @id java/android/cleartext-storage-shared-prefs
* @tags security
* external/cwe/cwe-312
*/

import java
import SensitiveStorage

from SensitiveSource data, SharedPreferencesEditor s, Expr input, Expr store
where
input = s.getAnInput() and
store = s.getAStore() and
data.flowsToCached(input) and
// Exclude results in test code.
not testMethod(store.getEnclosingCallable()) and
not testMethod(data.getEnclosingCallable())
select store, "'SharedPreferences' class $@ containing $@ is stored here. Data was added $@.", s,
s.toString(), data, "sensitive data", input, "here"
74 changes: 74 additions & 0 deletions java/ql/src/Security/CWE/CWE-312/SensitiveStorage.qll
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import java
import semmle.code.java.frameworks.Properties
import semmle.code.java.frameworks.JAXB
import semmle.code.java.frameworks.android.SharedPreferences
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.DataFlow3
import semmle.code.java.dataflow.DataFlow4
Expand Down Expand Up @@ -28,6 +29,10 @@ private class SensitiveSourceFlowConfig extends TaintTracking::Configuration {
m.getMethod() instanceof PropertiesSetPropertyMethod and sink.asExpr() = m.getArgument(1)
)
or
exists(MethodAccess m |
m.getMethod() instanceof SharedPreferencesSetMethod and sink.asExpr() = m.getArgument(1)
)
or
sink.asExpr() = getInstanceInput(_, _)
}

Expand Down Expand Up @@ -243,3 +248,72 @@ class Marshallable extends ClassStore {
)
}
}

/* Holds if the method call is a setter method of `SharedPreferences`. */
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
private predicate sharedPreferencesInput(DataFlow::Node sharedPrefs, Expr input) {
exists(MethodAccess m |
m.getMethod() instanceof SharedPreferencesSetMethod and
input = m.getArgument(1) and
sharedPrefs.asExpr() = m.getQualifier()
)
}

/* Holds if the method call is the save method of `SharedPreferences`. */
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
private predicate sharedPreferencesStore(DataFlow::Node sharedPrefs, Expr store) {
exists(MethodAccess m |
m.getMethod() instanceof SharedPreferencesStoreMethod and
store = m and
sharedPrefs.asExpr() = m.getQualifier()
)
}

/* Flow from `SharedPreferences` to the method call changing its value. */
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
class SharedPreferencesFlowConfig extends TaintTracking::Configuration {
SharedPreferencesFlowConfig() { this = "SensitiveStorage::SharedPreferencesFlowConfig" }

override predicate isSource(DataFlow::Node src) {
src.asExpr() instanceof SharedPreferencesEditor
}

override predicate isSink(DataFlow::Node sink) {
sharedPreferencesInput(sink, _) or
sharedPreferencesStore(sink, _)
}

override predicate isSanitizer(DataFlow::Node n) {
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
exists(MethodAccess ma |
ma.getMethod().getName().toLowerCase().matches("%encrypt%") and
n.asExpr() = ma.getAnArgument()
)
}
}

/** The call to get a `SharedPreferences.Editor` object, which can set shared preferences or be stored to device. */
class SharedPreferencesEditor extends MethodAccess {
SharedPreferencesEditor() {
this.getMethod() instanceof SharedPreferencesGetEditorMethod and
not exists(
MethodAccess cma // not exists `SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(...)`
|
cma.getQualifier().getType() instanceof TypeEncryptedSharedPreferences and
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
cma.getMethod().hasName("create") and
cma.getParent().(VariableAssign).getDestVar().getAnAccess() = this.getQualifier()
)
}

/** Gets an input, for example `input` in `editor.putString("password", password);`. */
Expr getAnInput() {
exists(SharedPreferencesFlowConfig conf, DataFlow::Node n |
sharedPreferencesInput(n, result) and
conf.hasFlow(DataFlow::exprNode(this), n)
)
}

/** Gets a store, for example `editor.commit();`. */
Expr getAStore() {
exists(SharedPreferencesFlowConfig conf, DataFlow::Node n |
sharedPreferencesStore(n, result) and
conf.hasFlow(DataFlow::exprNode(this), n)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* Definitions related to `android.content.SharedPreferences`. */
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
import semmle.code.java.Type

/* The interface `android.content.SharedPreferences` */
library class TypeSharedPreferences extends Interface {
Comment thread
luchua-bc marked this conversation as resolved.
Outdated
TypeSharedPreferences() { hasQualifiedName("android.content", "SharedPreferences") }
}

/* The class `androidx.security.crypto.EncryptedSharedPreferences`, which implements `SharedPreferences` with encryption support. */
library class TypeEncryptedSharedPreferences extends Class {
TypeEncryptedSharedPreferences() {
hasQualifiedName("androidx.security.crypto", "EncryptedSharedPreferences")
}
}

/* A getter method of `android.content.SharedPreferences`. */
library class SharedPreferencesGetMethod extends Method {
SharedPreferencesGetMethod() {
getDeclaringType() instanceof TypeSharedPreferences and
getName().matches("get%")
}
}

/* Returns `android.content.SharedPreferences.Editor` from the `edit` call of `android.content.SharedPreferences`. */
library class SharedPreferencesGetEditorMethod extends Method {
SharedPreferencesGetEditorMethod() {
getDeclaringType() instanceof TypeSharedPreferences and
hasName("edit") and
getReturnType() instanceof TypeSharedPreferencesEditor
}
}

/* Definitions related to `android.content.SharedPreferences.Editor`. */
library class TypeSharedPreferencesEditor extends Interface {
TypeSharedPreferencesEditor() { hasQualifiedName("android.content", "SharedPreferences$Editor") }
}

/* A setter method for `android.content.SharedPreferences`. */
library class SharedPreferencesSetMethod extends Method {
SharedPreferencesSetMethod() {
getDeclaringType() instanceof TypeSharedPreferencesEditor and
getName().matches("put%")
}
}

/* A setter method for `android.content.SharedPreferences`. */
library class SharedPreferencesStoreMethod extends Method {
SharedPreferencesStoreMethod() {
getDeclaringType() instanceof TypeSharedPreferencesEditor and
hasName(["commit", "apply"])
}
}
Comment thread
luchua-bc marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
| ClearTextStorageSharedPrefs.java:16:3:16:17 | commit(...) | 'SharedPreferences' class $@ containing $@ is stored here. Data was added $@. | ClearTextStorageSharedPrefs.java:13:19:13:36 | edit(...) | edit(...) | ClearTextStorageSharedPrefs.java:15:32:15:39 | password | sensitive data | ClearTextStorageSharedPrefs.java:15:32:15:39 | password | here |
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import androidx.security.crypto.MasterKey;
import androidx.security.crypto.EncryptedSharedPreferences;

/** Android activity that tests saving sensitive information in `SharedPreferences` */
public class ClearTextStorageSharedPrefs extends Activity {
// BAD - save sensitive information in cleartext
public void testSetSharedPrefs1(Context context, String name, String password) {
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
editor.putString("name", name);
editor.putString("password", password);
editor.commit();
}

// GOOD - save sensitive information in encrypted format
public void testSetSharedPrefs2(Context context, String name, String password) {
SharedPreferences sharedPrefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
Editor editor = sharedPrefs.edit();
editor.putString("name", encrypt(name));
editor.putString("password", encrypt(password));
editor.commit();
}

private static String encrypt(String cleartext) {
//Use an encryption or hashing algorithm in real world. The demo below just returns an arbitrary value.
String cipher = "whatever_encrypted";
return cipher;
}

// GOOD - save sensitive information using the built-in `EncryptedSharedPreferences` class in androidx.
public void testSetSharedPrefs3(Context context, String name, String password) {
MasterKey masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);

// Use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("name", name);
editor.putString("password", password);
editor.commit();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Security/CWE/CWE-312/ClearTextStorageSharedPrefs.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0
Loading