Merge pull request #6492 from atorralba/atorralba/android-cleartext-storage-database

Java: Create new query Cleartext storage of sensitive information in Android databases
This commit is contained in:
Tony Torralba
2022-02-02 16:23:05 +01:00
committed by GitHub
25 changed files with 679 additions and 45 deletions

View File

@@ -78,10 +78,12 @@ private import FlowSummary
private module Frameworks {
private import internal.ContainerFlow
private import semmle.code.java.frameworks.android.Android
private import semmle.code.java.frameworks.android.ContentProviders
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.android.Notifications
private import semmle.code.java.frameworks.android.Slice
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.android.Widget
private import semmle.code.java.frameworks.android.XssSinks
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Collections

View File

@@ -25,6 +25,7 @@ class ThriftIface extends Interface {
this.getFile() instanceof ThriftGeneratedFile
}
/** Gets an implementation of a method of this interface. */
Method getAnImplementingMethod() {
result.getDeclaringType().(Class).getASupertype+() = this and
result.overrides+(this.getAMethod()) and

View File

@@ -177,42 +177,6 @@ private class UriModel extends SummaryModelCsv {
}
}
private class ContentProviderSourceModels extends SourceModelCsv {
override predicate row(string row) {
row =
[
// ContentInterface models are here for backwards compatibility (it was removed in API 28)
"android.content;ContentInterface;true;call;(String,String,String,Bundle);;Parameter[0..3];contentprovider",
"android.content;ContentProvider;true;call;(String,String,String,Bundle);;Parameter[0..3];contentprovider",
"android.content;ContentProvider;true;call;(String,String,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;delete;(Uri,String,String[]);;Parameter[0..2];contentprovider",
"android.content;ContentInterface;true;delete;(Uri,Bundle);;Parameter[0..1];contentprovider",
"android.content;ContentProvider;true;delete;(Uri,Bundle);;Parameter[0..1];contentprovider",
"android.content;ContentInterface;true;getType;(Uri);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;getType;(Uri);;Parameter[0];contentprovider",
"android.content;ContentInterface;true;insert;(Uri,ContentValues,Bundle);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;insert;(Uri,ContentValues,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;insert;(Uri,ContentValues);;Parameter[0..1];contentprovider",
"android.content;ContentInterface;true;openAssetFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openAssetFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openAssetFile;(Uri,String);;Parameter[0];contentprovider",
"android.content;ContentInterface;true;openTypedAssetFile;(Uri,String,Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;openTypedAssetFile;(Uri,String,Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;openTypedAssetFile;(Uri,String,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentInterface;true;openFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openFile;(Uri,String);;Parameter[0];contentprovider",
"android.content;ContentInterface;true;query;(Uri,String[],Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;query;(Uri,String[],Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;query;(Uri,String[],String,String[],String);;Parameter[0..4];contentprovider",
"android.content;ContentProvider;true;query;(Uri,String[],String,String[],String,CancellationSignal);;Parameter[0..4];contentprovider",
"android.content;ContentInterface;true;update;(Uri,ContentValues,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;update;(Uri,ContentValues,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;update;(Uri,ContentValues,String,String[]);;Parameter[0..3];contentprovider"
]
}
}
/** Interface for classes whose instances can be written to and restored from a Parcel. */
class TypeParcelable extends Interface {
TypeParcelable() { this.hasQualifiedName("android.os", "Parcelable") }

View File

@@ -0,0 +1,59 @@
/**
* Provides classes and predicates for working with Content Providers.
*/
import java
import semmle.code.java.dataflow.ExternalFlow
/** The class `android.content.ContentValues`. */
class ContentValues extends Class {
ContentValues() { this.hasQualifiedName("android.content", "ContentValues") }
}
private class ContentProviderSourceModels extends SourceModelCsv {
override predicate row(string row) {
row =
[
// ContentInterface models are here for backwards compatibility (it was removed in API 28)
"android.content;ContentInterface;true;call;(String,String,String,Bundle);;Parameter[0..3];contentprovider",
"android.content;ContentProvider;true;call;(String,String,String,Bundle);;Parameter[0..3];contentprovider",
"android.content;ContentProvider;true;call;(String,String,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;delete;(Uri,String,String[]);;Parameter[0..2];contentprovider",
"android.content;ContentInterface;true;delete;(Uri,Bundle);;Parameter[0..1];contentprovider",
"android.content;ContentProvider;true;delete;(Uri,Bundle);;Parameter[0..1];contentprovider",
"android.content;ContentInterface;true;getType;(Uri);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;getType;(Uri);;Parameter[0];contentprovider",
"android.content;ContentInterface;true;insert;(Uri,ContentValues,Bundle);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;insert;(Uri,ContentValues,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;insert;(Uri,ContentValues);;Parameter[0..1];contentprovider",
"android.content;ContentInterface;true;openAssetFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openAssetFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openAssetFile;(Uri,String);;Parameter[0];contentprovider",
"android.content;ContentInterface;true;openTypedAssetFile;(Uri,String,Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;openTypedAssetFile;(Uri,String,Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;openTypedAssetFile;(Uri,String,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentInterface;true;openFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openFile;(Uri,String,CancellationSignal);;Parameter[0];contentprovider",
"android.content;ContentProvider;true;openFile;(Uri,String);;Parameter[0];contentprovider",
"android.content;ContentInterface;true;query;(Uri,String[],Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;query;(Uri,String[],Bundle,CancellationSignal);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;query;(Uri,String[],String,String[],String);;Parameter[0..4];contentprovider",
"android.content;ContentProvider;true;query;(Uri,String[],String,String[],String,CancellationSignal);;Parameter[0..4];contentprovider",
"android.content;ContentInterface;true;update;(Uri,ContentValues,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;update;(Uri,ContentValues,Bundle);;Parameter[0..2];contentprovider",
"android.content;ContentProvider;true;update;(Uri,ContentValues,String,String[]);;Parameter[0..3];contentprovider"
]
}
}
private class SummaryModels extends SummaryModelCsv {
override predicate row(string row) {
row =
[
"android.content;ContentValues;false;put;;;Argument[0];MapKey of Argument[-1];value",
"android.content;ContentValues;false;put;;;Argument[1];MapValue of Argument[-1];value",
"android.content;ContentValues;false;putAll;;;MapKey of Argument[0];MapKey of Argument[-1];value",
"android.content;ContentValues;false;putAll;;;MapValue of Argument[0];MapValue of Argument[-1];value"
]
}
}

View File

@@ -1,3 +1,5 @@
/** Provides classes and predicates for working with SQLite databases. */
import java
import Android
import semmle.code.java.dataflow.FlowSteps
@@ -24,6 +26,20 @@ class TypeDatabaseUtils extends Class {
TypeDatabaseUtils() { hasQualifiedName("android.database", "DatabaseUtils") }
}
/**
* The class `android.database.sqlite.SQLiteOpenHelper`.
*/
class TypeSQLiteOpenHelper extends Class {
TypeSQLiteOpenHelper() { this.hasQualifiedName("android.database.sqlite", "SQLiteOpenHelper") }
}
/**
* The class `android.database.sqlite.SQLiteStatement`.
*/
class TypeSQLiteStatement extends Class {
TypeSQLiteStatement() { this.hasQualifiedName("android.database.sqlite", "SQLiteStatement") }
}
private class SQLiteSinkCsv extends SinkModelCsv {
override predicate row(string row) {
row =

View File

@@ -0,0 +1,23 @@
/** Provides classes and predicates for working with Android widgets. */
import java
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.FlowSources
private class AndroidWidgetSourceModels extends SourceModelCsv {
override predicate row(string row) {
row = "android.widget;EditText;true;getText;;;ReturnValue;android-widget"
}
}
private class DefaultAndroidWidgetSources extends RemoteFlowSource {
DefaultAndroidWidgetSources() { sourceNode(this, "android-widget") }
override string getSourceType() { result = "Android widget source" }
}
private class AndroidWidgetSummaryModels extends SummaryModelCsv {
override predicate row(string row) {
row = "android.widget;EditText;true;getText;;;Argument[-1];ReturnValue;taint"
}
}

View File

@@ -15,6 +15,7 @@ class StrutsAnnotation extends Annotation {
class StrutsActionAnnotation extends StrutsAnnotation {
StrutsActionAnnotation() { this.getType().hasName("Action") }
/** Gets a callable annotated with this annotation. */
Callable getActionCallable() {
result = this.getAnnotatedElement()
or

View File

@@ -0,0 +1,131 @@
/** Provides classes and predicates to reason about cleartext storage in Android databases. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.android.ContentProviders
import semmle.code.java.frameworks.android.Intent
import semmle.code.java.frameworks.android.SQLite
import semmle.code.java.security.CleartextStorageQuery
private class LocalDatabaseCleartextStorageSink extends CleartextStorageSink {
LocalDatabaseCleartextStorageSink() { localDatabaseInput(_, this.asExpr()) }
}
private class LocalDatabaseCleartextStorageStep extends CleartextStorageAdditionalTaintStep {
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
// EditText.getText() return type is parsed as `Object`, so we need to
// add a taint step for `Object.toString` to model `editText.getText().toString()`
exists(MethodAccess ma, Method m |
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeObject and
m.hasName("toString")
|
n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma
)
}
}
/** The creation of an object that can be used to store data in a local database. */
class LocalDatabaseOpenMethodAccess extends Storable, Call {
LocalDatabaseOpenMethodAccess() {
exists(Method m | this.(MethodAccess).getMethod() = m |
m.getDeclaringType().getASupertype*() instanceof TypeSQLiteOpenHelper and
m.hasName("getWritableDatabase")
or
m.getDeclaringType() instanceof TypeSQLiteDatabase and
m.hasName(["create", "openDatabase", "openOrCreateDatabase", "compileStatement"])
or
m.getDeclaringType().getASupertype*() instanceof TypeContext and
m.hasName("openOrCreateDatabase")
)
or
this.(ClassInstanceExpr).getConstructedType() instanceof ContentValues
}
override Expr getAnInput() {
exists(LocalDatabaseFlowConfig config, DataFlow::Node database |
localDatabaseInput(database, result) and
config.hasFlow(DataFlow::exprNode(this), database)
)
}
override Expr getAStore() {
exists(LocalDatabaseFlowConfig config, DataFlow::Node database |
localDatabaseStore(database, result) and
config.hasFlow(DataFlow::exprNode(this), database)
)
}
}
/** A method that is both a database input and a database store. */
private class LocalDatabaseInputStoreMethod extends Method {
LocalDatabaseInputStoreMethod() {
this.getDeclaringType() instanceof TypeSQLiteDatabase and
this.getName().matches("exec%SQL")
}
}
/**
* Holds if `input` is a value being prepared for being stored into the SQLite dataabse `database`.
* This can be done using prepared statements, using the class `ContentValues`, or directly
* appending `input` to a SQL query.
*/
private predicate localDatabaseInput(DataFlow::Node database, Argument input) {
exists(Method m | input.getCall().getCallee() = m |
m instanceof LocalDatabaseInputStoreMethod and
database.asExpr() = input.getCall().getQualifier()
or
m.getDeclaringType() instanceof TypeSQLiteDatabase and
m.hasName("compileStatement") and
database.asExpr() = input.getCall()
or
m.getDeclaringType() instanceof ContentValues and
m.hasName("put") and
input.getPosition() = 1 and
database.asExpr() = input.getCall().getQualifier()
)
}
/**
* Holds if `store` is a method call for storing a previously appended input value,
* either through the use of prepared statements, via the `ContentValues` class, or
* directly executing a raw SQL query.
*/
private predicate localDatabaseStore(DataFlow::Node database, MethodAccess store) {
exists(Method m | store.getMethod() = m |
m instanceof LocalDatabaseInputStoreMethod and
database.asExpr() = store.getQualifier()
or
m.getDeclaringType() instanceof TypeSQLiteDatabase and
m.getName().matches(["insert%", "replace%", "update%"]) and
database.asExpr() = store.getAnArgument() and
database.getType() instanceof ContentValues
or
m.getDeclaringType() instanceof TypeSQLiteStatement and
m.hasName(["executeInsert", "executeUpdateDelete"]) and
database.asExpr() = store.getQualifier()
)
}
private class LocalDatabaseFlowConfig extends DataFlow::Configuration {
LocalDatabaseFlowConfig() { this = "LocalDatabaseFlowConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof LocalDatabaseOpenMethodAccess
}
override predicate isSink(DataFlow::Node sink) {
localDatabaseInput(sink, _) or
localDatabaseStore(sink, _)
}
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
// Adds a step for tracking databases through field flow, that is, a database is opened and
// assigned to a field, and then an input or store method is called on that field elsewhere.
exists(Field f |
f.getType() instanceof TypeSQLiteDatabase and
f.getAnAssignedValue() = n1.asExpr() and
f = n2.asExpr().(FieldRead).getField()
)
}
}

View File

@@ -14,7 +14,11 @@
import java
private string suspicious() {
result = ["%password%", "%passwd%", "%account%", "%accnt%", "%trusted%"]
result =
[
"%password%", "%passwd%", "pwd", "%account%", "%accnt%", "%trusted%", "%refresh%token%",
"%secret%token"
]
}
private string nonSuspicious() {