Skip to content

Commit 3c41548

Browse files
authored
Merge pull request #4336 from aibaars/android-database
Java: add Android database taint and SQL injection sinks
2 parents bc1d3de + 8971092 commit 3c41548

File tree

22 files changed

+1193
-10
lines changed

22 files changed

+1193
-10
lines changed

java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ private import semmle.code.java.dataflow.DefUse
66
private import semmle.code.java.security.SecurityTests
77
private import semmle.code.java.security.Validation
88
private import semmle.code.java.frameworks.android.Intent
9+
private import semmle.code.java.frameworks.android.SQLite
910
private import semmle.code.java.frameworks.Guice
1011
private import semmle.code.java.frameworks.Protobuf
1112
private import semmle.code.java.frameworks.spring.SpringController
@@ -392,6 +393,14 @@ private predicate taintPreservingQualifierToMethod(Method m) {
392393
or
393394
m.getDeclaringType() instanceof TypeFormatter and
394395
m.hasName(["format", "out"])
396+
or
397+
m.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
398+
// buildQuery(String[] projectionIn, String selection, String groupBy, String having, String sortOrder, String limit)
399+
// buildQuery(String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)
400+
// buildUnionQuery(String[] subQueries, String sortOrder, String limit)
401+
// buildUnionSubQuery(String typeDiscriminatorColumn, String[] unionColumns, Set<String> columnsPresentInTable, int computedColumnsOffset, String typeDiscriminatorValue, String selection, String[] selectionArgs, String groupBy, String having)
402+
// buildUnionSubQuery(String typeDiscriminatorColumn, String[] unionColumns, Set<String> columnsPresentInTable, int computedColumnsOffset, String typeDiscriminatorValue, String selection, String groupBy, String having)
403+
m.hasName(["buildQuery", "buildUnionQuery", "buildUnionSubQuery"])
395404
}
396405

397406
private class StringReplaceMethod extends Method {
@@ -455,6 +464,17 @@ private predicate taintPreservingArgumentToMethod(Method method) {
455464
or
456465
method.getDeclaringType() instanceof TypeFormatter and
457466
method.hasName("format")
467+
or
468+
method.getDeclaringType() instanceof TypeDatabaseUtils and
469+
// String[] appendSelectionArgs(String[] originalValues, String[] newValues)
470+
// String concatenateWhere(String a, String b)
471+
method.hasName(["appendSelectionArgs", "concatenateWhere"])
472+
or
473+
method.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
474+
// buildQuery(String[] projectionIn, String selection, String groupBy, String having, String sortOrder, String limit)
475+
// buildQuery(String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)
476+
// buildUnionQuery(String[] subQueries, String sortOrder, String limit)
477+
method.hasName(["buildQuery", "buildUnionQuery"])
458478
}
459479

460480
/**
@@ -568,6 +588,27 @@ private predicate taintPreservingArgumentToMethod(Method method, int arg) {
568588
method.getDeclaringType().hasQualifiedName("java.io", "StringWriter") and
569589
method.hasName("append") and
570590
arg = 0
591+
or
592+
method.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
593+
(
594+
// static buildQueryString(boolean distinct, String tables, String[] columns, String where, String groupBy, String having, String orderBy, String limit)
595+
method.hasName("buildQueryString") and arg = [1 .. method.getNumberOfParameters()]
596+
or
597+
// buildUnionSubQuery(String typeDiscriminatorColumn, String[] unionColumns, Set<String> columnsPresentInTable, int computedColumnsOffset, String typeDiscriminatorValue, String selection, String[] selectionArgs, String groupBy, String having)
598+
// buildUnionSubQuery(String typeDiscriminatorColumn, String[] unionColumns, Set<String> columnsPresentInTable, int computedColumnsOffset, String typeDiscriminatorValue, String selection, String groupBy, String having)
599+
method.hasName("buildUnionSubQuery") and
600+
arg = [0 .. method.getNumberOfParameters()] and
601+
arg != 3
602+
)
603+
or
604+
(
605+
method.getDeclaringType() instanceof AndroidContentProvider or
606+
method.getDeclaringType() instanceof AndroidContentResolver
607+
) and
608+
// Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)
609+
// Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
610+
method.hasName("query") and
611+
arg = 0
571612
}
572613

573614
/**
@@ -620,6 +661,12 @@ private predicate taintPreservingArgToArg(Method method, int input, int output)
620661
method.getNumberOfParameters() > 1 and
621662
input = method.getNumberOfParameters() - 1 and
622663
output = 0
664+
or
665+
method.getDeclaringType() instanceof TypeSQLiteQueryBuilder and
666+
// static appendColumns(StringBuilder s, String[] columns)
667+
method.hasName("appendColumns") and
668+
input = 1 and
669+
output = 0
623670
}
624671

625672
/**
@@ -671,6 +718,14 @@ private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
671718
arg = 0 and
672719
append.getDeclaringType().hasQualifiedName("java.io", "StringWriter")
673720
)
721+
or
722+
method.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
723+
// setProjectionMap(Map<String, String> columnMap)
724+
// setTables(String inTables)
725+
// appendWhere(CharSequence inWhere)
726+
// appendWhereStandalone(CharSequence inWhere)
727+
method.hasName(["setProjectionMap", "setTables", "appendWhere", "appendWhereStandalone"]) and
728+
arg = 0
674729
}
675730

676731
/** A comparison or equality test with a constant. */

java/ql/src/semmle/code/java/frameworks/android/Android.qll

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ class AndroidComponent extends Class {
1414
this.getASupertype*().hasQualifiedName("android.app", "Activity") or
1515
this.getASupertype*().hasQualifiedName("android.app", "Service") or
1616
this.getASupertype*().hasQualifiedName("android.content", "BroadcastReceiver") or
17-
this.getASupertype*().hasQualifiedName("android.content", "ContentProvider")
17+
this.getASupertype*().hasQualifiedName("android.content", "ContentProvider") or
18+
this.getASupertype*().hasQualifiedName("android.content", "ContentResolver")
1819
}
1920

2021
/** The XML element corresponding to this Android component. */
@@ -52,3 +53,10 @@ class AndroidContentProvider extends AndroidComponent {
5253
this.getASupertype*().hasQualifiedName("android.content", "ContentProvider")
5354
}
5455
}
56+
57+
/** An Android content resolver. */
58+
class AndroidContentResolver extends AndroidComponent {
59+
AndroidContentResolver() {
60+
this.getASupertype*().hasQualifiedName("android.content", "ContentResolver")
61+
}
62+
}
Lines changed: 191 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,228 @@
11
import java
2+
import Android
23

4+
/**
5+
* The class `android.database.sqlite.SQLiteDatabase`.
6+
*/
37
class TypeSQLiteDatabase extends Class {
48
TypeSQLiteDatabase() { hasQualifiedName("android.database.sqlite", "SQLiteDatabase") }
59
}
610

11+
/**
12+
* The class `android.database.sqlite.SQLiteQueryBuilder`.
13+
*/
14+
class TypeSQLiteQueryBuilder extends Class {
15+
TypeSQLiteQueryBuilder() { hasQualifiedName("android.database.sqlite", "SQLiteQueryBuilder") }
16+
}
17+
18+
/**
19+
* The class `android.database.DatabaseUtils`.
20+
*/
21+
class TypeDatabaseUtils extends Class {
22+
TypeDatabaseUtils() { hasQualifiedName("android.database", "DatabaseUtils") }
23+
}
24+
725
abstract class SQLiteRunner extends Method {
826
abstract int sqlIndex();
927
}
1028

11-
class ExecSqlMethod extends SQLiteRunner {
29+
private class ExecSqlMethod extends SQLiteRunner {
1230
ExecSqlMethod() {
1331
this.getDeclaringType() instanceof TypeSQLiteDatabase and
14-
this.getName() = "execSql"
32+
// execPerConnectionSQL(String sql, Object[] bindArgs)
33+
// execSQL(String sql)
34+
// execSQL(String sql, Object[] bindArgs)
35+
this.hasName(["execPerConnectionSQL", "execSQL"])
1536
}
1637

1738
override int sqlIndex() { result = 0 }
1839
}
1940

20-
class QueryMethod extends SQLiteRunner {
41+
private class QueryMethod extends SQLiteRunner {
2142
QueryMethod() {
2243
this.getDeclaringType() instanceof TypeSQLiteDatabase and
23-
this.getName().matches("rawQuery%")
44+
this.hasName(["query", "queryWithFactory"])
2445
}
2546

2647
override int sqlIndex() {
48+
// query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
49+
// query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal)
50+
// query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
51+
// query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
2752
this.getName() = "query" and
28-
(if this.getParameter(0).getType() instanceof TypeString then result = 2 else result = 3)
53+
(
54+
if this.getParameter(0).getType() instanceof TypeString
55+
then result = [0, 1, 2, 4, 5, 6, 7]
56+
else result = [1, 2, 3, 5, 6, 7, 8]
57+
)
2958
or
30-
this.getName() = "queryWithFactory" and result = 4
59+
// queryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal)
60+
// queryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
61+
this.getName() = "queryWithFactory" and result = [2, 3, 4, 6, 7, 8, 9]
3162
}
3263
}
3364

34-
class RawQueryMethod extends SQLiteRunner {
65+
private class RawQueryMethod extends SQLiteRunner {
3566
RawQueryMethod() {
3667
this.getDeclaringType() instanceof TypeSQLiteDatabase and
37-
this.getName().matches("rawQuery%")
68+
this.hasName(["rawQuery", "rawQueryWithFactory"])
3869
}
3970

4071
override int sqlIndex() {
72+
// rawQuery(String sql, String[] selectionArgs, CancellationSignal cancellationSignal)
73+
// rawQuery(String sql, String[] selectionArgs)
4174
this.getName() = "rawQuery" and result = 0
4275
or
76+
// rawQueryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable, CancellationSignal cancellationSignal)
77+
// rawQueryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable)
4378
this.getName() = "rawQueryWithFactory" and result = 1
4479
}
4580
}
81+
82+
private class CompileStatementMethod extends SQLiteRunner {
83+
CompileStatementMethod() {
84+
this.getDeclaringType() instanceof TypeSQLiteDatabase and
85+
// compileStatement(String sql)
86+
this.hasName("compileStatement")
87+
}
88+
89+
override int sqlIndex() { result = 0 }
90+
}
91+
92+
private class DeleteMethod extends SQLiteRunner {
93+
DeleteMethod() {
94+
this.getDeclaringType() instanceof TypeSQLiteDatabase and
95+
// delete(String table, String whereClause, String[] whereArgs)
96+
this.hasName("delete")
97+
}
98+
99+
override int sqlIndex() { result = 1 }
100+
}
101+
102+
private class UpdateMethod extends SQLiteRunner {
103+
UpdateMethod() {
104+
this.getDeclaringType() instanceof TypeSQLiteDatabase and
105+
// update(String table, ContentValues values, String whereClause, String[] whereArgs)
106+
// updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm)
107+
this.hasName(["update", "updateWithOnConflict"])
108+
}
109+
110+
override int sqlIndex() { result = 2 }
111+
}
112+
113+
private class ForQueryMethod extends SQLiteRunner {
114+
ForQueryMethod() {
115+
// (blobFileDescriptor|long|string)ForQuery(SQLiteDatabase db, String query, String[] selectionArgs)
116+
this.getDeclaringType() instanceof TypeDatabaseUtils and
117+
this.hasName(["blobFileDescriptorForQuery", "longForQuery", "stringForQuery"]) and
118+
this.getNumberOfParameters() = 3
119+
}
120+
121+
override int sqlIndex() { result = 1 }
122+
}
123+
124+
private class CreateDbFromSqlStatementsMethod extends SQLiteRunner {
125+
CreateDbFromSqlStatementsMethod() {
126+
// createDbFromSqlStatements(Context context, String dbName, int dbVersion, String sqlStatements)
127+
this.getDeclaringType() instanceof TypeDatabaseUtils and
128+
this.hasName("createDbFromSqlStatements")
129+
}
130+
131+
override int sqlIndex() { result = 3 }
132+
}
133+
134+
private class QueryNumEntriesMethod extends SQLiteRunner {
135+
QueryNumEntriesMethod() {
136+
// queryNumEntries(SQLiteDatabase db, String table, String selection)
137+
// queryNumEntries(SQLiteDatabase db, String table, String selection, String[] selectionArgs)
138+
this.getDeclaringType() instanceof TypeDatabaseUtils and
139+
this.hasName("queryNumEntries")
140+
}
141+
142+
override int sqlIndex() { result = 2 }
143+
}
144+
145+
private class QueryBuilderDeleteMethod extends SQLiteRunner {
146+
QueryBuilderDeleteMethod() {
147+
// delete(SQLiteDatabase db, String selection, String[] selectionArgs)
148+
this.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
149+
this.hasName("delete")
150+
}
151+
152+
override int sqlIndex() { result = [-1, 1] }
153+
}
154+
155+
private class QueryBuilderInsertMethod extends SQLiteRunner {
156+
QueryBuilderInsertMethod() {
157+
// insert(SQLiteDatabase db, ContentValues values)
158+
this.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
159+
this.hasName("insert")
160+
}
161+
162+
override int sqlIndex() { result = -1 }
163+
}
164+
165+
private class QueryBuilderQueryMethod extends SQLiteRunner {
166+
QueryBuilderQueryMethod() {
167+
// query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder)
168+
// query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)
169+
// query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit, CancellationSignal cancellationSignal)
170+
this.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
171+
this.hasName("query")
172+
}
173+
174+
override int sqlIndex() { result = [-1, 2, 4, 5, 6, 7] }
175+
}
176+
177+
private class QueryBuilderUpdateMethod extends SQLiteRunner {
178+
QueryBuilderUpdateMethod() {
179+
// update(SQLiteDatabase db, ContentValues values, String selection, String[] selectionArgs)
180+
this.getDeclaringType().getASourceSupertype*() instanceof TypeSQLiteQueryBuilder and
181+
this.hasName("update")
182+
}
183+
184+
override int sqlIndex() { result = [-1, 2] }
185+
}
186+
187+
private class ContentProviderDeleteMethod extends SQLiteRunner {
188+
ContentProviderDeleteMethod() {
189+
// delete(Uri uri, String selection, String[] selectionArgs)
190+
(
191+
this.getDeclaringType() instanceof AndroidContentProvider or
192+
this.getDeclaringType() instanceof AndroidContentResolver
193+
) and
194+
this.hasName("delete") and
195+
this.getNumberOfParameters() = 3
196+
}
197+
198+
override int sqlIndex() { result = 1 }
199+
}
200+
201+
private class ContentProviderQueryMethod extends SQLiteRunner {
202+
ContentProviderQueryMethod() {
203+
// query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)
204+
// query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
205+
(
206+
this.getDeclaringType() instanceof AndroidContentProvider or
207+
this.getDeclaringType() instanceof AndroidContentResolver
208+
) and
209+
this.hasName("query") and
210+
this.getNumberOfParameters() = [5, 6]
211+
}
212+
213+
override int sqlIndex() { result = 2 }
214+
}
215+
216+
private class ContentProviderUpdateMethod extends SQLiteRunner {
217+
ContentProviderUpdateMethod() {
218+
// update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
219+
(
220+
this.getDeclaringType() instanceof AndroidContentProvider or
221+
this.getDeclaringType() instanceof AndroidContentResolver
222+
) and
223+
this.hasName("update") and
224+
this.getNumberOfParameters() = 4
225+
}
226+
227+
override int sqlIndex() { result = 2 }
228+
}

java/ql/src/semmle/code/java/security/QueryInjection.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ private class SqlInjectionSink extends QueryInjectionSink {
3434
or
3535
exists(MethodAccess ma, Method m, int index |
3636
ma.getMethod() = m and
37-
ma.getArgument(index) = this.asExpr()
37+
if index = -1
38+
then this.asExpr() = ma.getQualifier()
39+
else ma.getArgument(index) = this.asExpr()
3840
|
3941
index = m.(SQLiteRunner).sqlIndex()
4042
or

0 commit comments

Comments
 (0)