Skip to content

Commit f0604e2

Browse files
committed
Added query for Cleartext Storage in Android Database
1 parent 117795c commit f0604e2

26 files changed

+611
-10
lines changed

java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ private module Frameworks {
132132
private import semmle.code.java.frameworks.Hibernate
133133
private import semmle.code.java.frameworks.jOOQ
134134
private import semmle.code.java.frameworks.spring.SpringHttp
135+
private import semmle.code.java.frameworks.android.ContentProviders
136+
private import semmle.code.java.frameworks.android.Widget
135137
}
136138

137139
private predicate sourceModelCsv(string row) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Provides classes and predicates for working with Content Providers.
3+
*/
4+
5+
import java
6+
import semmle.code.java.dataflow.ExternalFlow
7+
8+
/** The class `android.content.ContentValues`. */
9+
class ContentValues extends Class {
10+
ContentValues() { this.hasQualifiedName("android.content", "ContentValues") }
11+
}
12+
13+
private class SummaryModels extends SummaryModelCsv {
14+
override predicate row(string row) {
15+
row =
16+
[
17+
"android.content;ContentValues;false;put;;;Argument[0];MapKey of Argument[-1];value",
18+
"android.content;ContentValues;false;put;;;Argument[1];MapValue of Argument[-1];value",
19+
"android.content;ContentValues;false;putAll;;;MapKey of Argument[0];MapKey of Argument[-1];value",
20+
"android.content;ContentValues;false;putAll;;;MapValue of Argument[0];MapValue of Argument[-1];value"
21+
]
22+
}
23+
}

java/ql/lib/semmle/code/java/frameworks/android/SQLite.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ class TypeDatabaseUtils extends Class {
2424
TypeDatabaseUtils() { hasQualifiedName("android.database", "DatabaseUtils") }
2525
}
2626

27+
class TypeSQLiteOpenHelper extends Class {
28+
TypeSQLiteOpenHelper() { this.hasQualifiedName("android.database.sqlite", "SQLiteOpenHelper") }
29+
}
30+
31+
class TypeSQLiteStatement extends Class {
32+
TypeSQLiteStatement() { this.hasQualifiedName("android.database.sqlite", "SQLiteStatement") }
33+
}
34+
2735
private class SQLiteSinkCsv extends SinkModelCsv {
2836
override predicate row(string row) {
2937
row =
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import java
2+
import semmle.code.java.dataflow.ExternalFlow
3+
import semmle.code.java.dataflow.FlowSources
4+
5+
private class AndroidWidgetSourceModels extends SourceModelCsv {
6+
override predicate row(string row) {
7+
row = ["android.widget;EditText;true;getText;;;ReturnValue;android-widget"]
8+
}
9+
}
10+
11+
private class DefaultAndroidWidgetSources extends RemoteFlowSource {
12+
DefaultAndroidWidgetSources() { sourceNode(this, "android-widget") }
13+
14+
override string getSourceType() { result = "Android widget source" }
15+
}
16+
17+
private class AndroidWidgetSummaryModels extends SummaryModelCsv {
18+
override predicate row(string row) {
19+
row = ["android.widget;EditText;true;getText;;;Argument[-1];ReturnValue;taint"]
20+
}
21+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/** Provides classes and predicates to reason about cleartext storage in Android databases. */
2+
3+
import java
4+
import semmle.code.java.dataflow.DataFlow
5+
import semmle.code.java.frameworks.android.ContentProviders
6+
import semmle.code.java.frameworks.android.Intent
7+
import semmle.code.java.frameworks.android.SQLite
8+
import semmle.code.java.security.CleartextStorageQuery
9+
10+
private class LocalDatabaseCleartextStorageSink extends CleartextStorageSink {
11+
LocalDatabaseCleartextStorageSink() { localDatabaseInput(_, this.asExpr()) }
12+
}
13+
14+
private class LocalDatabaseCleartextStorageStep extends CleartextStorageAdditionalTaintStep {
15+
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
16+
// EditText.getText() return type is parsed as `Object`, so we need to
17+
// add a taint step for `Object.toString` to model `editText.getText().toString()`
18+
exists(MethodAccess ma, Method m |
19+
ma.getMethod() = m and
20+
m.getDeclaringType() instanceof TypeObject and
21+
m.hasName("toString")
22+
|
23+
n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma
24+
)
25+
}
26+
}
27+
28+
/** The creation of an object that can be used to store data in a local database. */
29+
class LocalDatabaseOpenMethodAccess extends Storable, Call {
30+
LocalDatabaseOpenMethodAccess() {
31+
exists(Method m | this.(MethodAccess).getMethod() = m |
32+
m.getDeclaringType().getASupertype*() instanceof TypeSQLiteOpenHelper and
33+
m.hasName("getWritableDatabase")
34+
or
35+
m.getDeclaringType() instanceof TypeSQLiteDatabase and
36+
m.hasName(["create", "open%Database", "compileStatement"])
37+
or
38+
m.getDeclaringType().getASupertype*() instanceof TypeContext and
39+
m.hasName("openOrCreateDatabase")
40+
)
41+
or
42+
this.(ClassInstanceExpr).getConstructedType() instanceof ContentValues
43+
}
44+
45+
override Expr getAnInput() {
46+
exists(LocalDatabaseFlowConfig config, DataFlow::Node database |
47+
localDatabaseInput(database, result) and
48+
config.hasFlow(DataFlow::exprNode(this), database)
49+
)
50+
}
51+
52+
override Expr getAStore() {
53+
exists(LocalDatabaseFlowConfig config, DataFlow::Node database |
54+
localDatabaseStore(database, result) and
55+
config.hasFlow(DataFlow::exprNode(this), database)
56+
)
57+
}
58+
}
59+
60+
/** A method that is both a database input and a database store. */
61+
private class LocalDatabaseInputStoreMethod extends Method {
62+
LocalDatabaseInputStoreMethod() {
63+
this.getDeclaringType() instanceof TypeSQLiteDatabase and
64+
this.getName().matches("exec%SQL")
65+
}
66+
}
67+
68+
private predicate localDatabaseInput(DataFlow::Node database, Argument input) {
69+
exists(Method m | input.getCall().(MethodAccess).getMethod() = m |
70+
m instanceof LocalDatabaseInputStoreMethod and
71+
database.asExpr() = input.getCall().getQualifier()
72+
or
73+
m.getDeclaringType() instanceof TypeSQLiteDatabase and
74+
m.hasName("compileStatement") and
75+
database.asExpr() = input.getCall()
76+
or
77+
m.getDeclaringType() instanceof ContentValues and
78+
m.hasName("put") and
79+
input.getPosition() = 1 and
80+
database.asExpr() = input.getCall().getQualifier()
81+
)
82+
}
83+
84+
private predicate localDatabaseStore(DataFlow::Node database, MethodAccess store) {
85+
exists(Method m | store.getMethod() = m |
86+
m instanceof LocalDatabaseInputStoreMethod and
87+
database.asExpr() = store.getQualifier()
88+
or
89+
m.getDeclaringType() instanceof TypeSQLiteDatabase and
90+
m.getName().matches(["insert%", "replace%", "update%"]) and
91+
database.asExpr() = store.getAnArgument() and
92+
database.getType() instanceof ContentValues
93+
or
94+
m.getDeclaringType() instanceof TypeSQLiteStatement and
95+
m.hasName(["executeInsert", "executeUpdateDelete"]) and
96+
database.asExpr() = store.getQualifier()
97+
)
98+
}
99+
100+
private class LocalDatabaseFlowConfig extends DataFlow::Configuration {
101+
LocalDatabaseFlowConfig() { this = "LocalDatabaseFlowConfig" }
102+
103+
override predicate isSource(DataFlow::Node source) {
104+
source.asExpr() instanceof LocalDatabaseOpenMethodAccess
105+
}
106+
107+
override predicate isSink(DataFlow::Node sink) {
108+
localDatabaseInput(sink, _) or
109+
localDatabaseStore(sink, _)
110+
}
111+
112+
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
113+
exists(Field f |
114+
f.getType() instanceof TypeSQLiteDatabase and
115+
f.getAnAssignedValue() = n1.asExpr() and
116+
f = n2.asExpr().(FieldRead).getField()
117+
)
118+
}
119+
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,19 @@
1414
import java
1515

1616
private string suspicious() {
17-
result = ["%password%", "%passwd%", "%account%", "%accnt%", "%trusted%"]
17+
result =
18+
[
19+
"%password%", "%passwd%", "pwd", "%account%", "%accnt%", "%trusted%", "%refresh%token%",
20+
"%secret%token"
21+
]
1822
}
1923

2024
private string nonSuspicious() {
2125
result = "%hashed%" or
2226
result = "%encrypted%" or
23-
result = "%crypt%"
27+
result = "%crypt%" or
28+
result = "%create table%" or
29+
result = "%drop table%"
2430
}
2531

2632
/**
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
public void sqliteStorageUnsafe(Context ctx, String name, String password) {
2+
// BAD - sensitive information saved in cleartext.
3+
SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null);
4+
db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, password});
5+
}
6+
7+
public void sqliteStorageSafe(Context ctx, String name, String password) {
8+
// GOOD - sensitive information encrypted with a custom method.
9+
SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null);
10+
db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, encrypt(password)});
11+
}
12+
13+
public void sqlCipherStorageSafe(String name, String password, String databasePassword) {
14+
// GOOD - sensitive information saved using SQLCipher.
15+
net.sqlcipher.database.SQLiteDatabase db =
16+
net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase("test", databasePassword, null);
17+
db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, password});
18+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
SQLite is a lightweight database engine commonly used in Android devices to store data. By itself, SQLite does not offer any encryption mechanism by default and stores all data in plaintext, which introduces a risk if sensitive data like credentials, authentication tokens or personal identifiable information (PII) are directly stored in a SQLite database. The information could 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.
6+
</p>
7+
</overview>
8+
9+
<recommendation>
10+
<p>
11+
Use <code>SQLCipher</code> or similar libraries to add encryption capabilities to SQLite. Alternatively, encrypt sensitive data using cryptographicaly secure algorithms before storing it in the database.
12+
</p>
13+
</recommendation>
14+
15+
<example>
16+
<p>
17+
In the first example, sensitive user information is stored in cleartext.
18+
</p>
19+
20+
<p>
21+
In the second and third examples, the code encrypts sensitive information before saving it to the database.
22+
</p>
23+
<sample src="CleartextStorageAndroidDatabase.java" />
24+
</example>
25+
26+
<references>
27+
<li>
28+
Android Developers:
29+
<a href="https://developer.android.com/topic/security/data">Work with data more securely</a>
30+
</li>
31+
<li>
32+
SQLCipher:
33+
<a href="https://www.zetetic.net/sqlcipher/sqlcipher-for-android/">Android Application Integration</a>
34+
</li>
35+
</references>
36+
</qhelp>
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 a local database on Android
3+
* @description Cleartext Storage of Sensitive Information using
4+
* a local database 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-database
10+
* @tags security
11+
* external/cwe/cwe-312
12+
*/
13+
14+
import java
15+
import semmle.code.java.security.CleartextStorageAndroidDatabaseQuery
16+
17+
from SensitiveSource data, LocalDatabaseOpenMethodAccess s, Expr input, Expr store
18+
where
19+
input = s.getAnInput() and
20+
store = s.getAStore() and
21+
data.flowsToCached(input)
22+
select store, "SQLite database $@ containing $@ is stored $@. Data was added $@.", s, s.toString(),
23+
data, "sensitive data", store, "here", input, "here"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* A new query "Cleartext storage of sensitive information using a local database on Android" (`java/android/cleartext-storage-database`) has been added. This query finds instances of sensitive data being stored in local databases without encryption, which may expose it to attackers or malicious applications.

0 commit comments

Comments
 (0)