| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() | ||
|
Comment thread
atorralba marked this conversation as resolved.
|
||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| public void sqliteStorageUnsafe(Context ctx, String name, String password) { | ||
| // BAD - sensitive information saved in cleartext. | ||
| SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null); | ||
| db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, password}); | ||
| } | ||
|
|
||
| public void sqliteStorageSafe(Context ctx, String name, String password) { | ||
| // GOOD - sensitive information encrypted with a custom method. | ||
| SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null); | ||
| db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, encrypt(password)}); | ||
| } | ||
|
|
||
| public void sqlCipherStorageSafe(String name, String password, String databasePassword) { | ||
| // GOOD - sensitive information saved using SQLCipher. | ||
| net.sqlcipher.database.SQLiteDatabase db = | ||
| net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase("test", databasePassword, null); | ||
| db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, password}); | ||
| } | ||
|
|
||
| private static String encrypt(String cleartext) { | ||
| // Use an encryption or strong hashing algorithm in the real world. | ||
| // The example below just returns a SHA-256 hash. | ||
| MessageDigest digest = MessageDigest.getInstance("SHA-256"); | ||
| byte[] hash = digest.digest(cleartext.getBytes(StandardCharsets.UTF_8)); | ||
| String encoded = Base64.getEncoder().encodeToString(hash); | ||
| return encoded; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd"> | ||
| <qhelp> | ||
| <overview> | ||
| <p> | ||
| 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 cleartext, 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. | ||
| </p> | ||
| </overview> | ||
|
|
||
| <recommendation> | ||
| <p> | ||
| Use <code>SQLCipher</code> or similar libraries to add encryption capabilities to SQLite. Alternatively, encrypt sensitive data using cryptographically secure algorithms before storing it in the database. | ||
| </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 it to the database. | ||
| </p> | ||
| <sample src="CleartextStorageAndroidDatabase.java" /> | ||
| </example> | ||
|
|
||
| <references> | ||
| <li> | ||
| Android Developers: | ||
| <a href="https://developer.android.com/topic/security/data">Work with data more securely</a> | ||
| </li> | ||
| <li> | ||
| SQLCipher: | ||
| <a href="https://www.zetetic.net/sqlcipher/sqlcipher-for-android/">Android Application Integration</a> | ||
| </li> | ||
| </references> | ||
| </qhelp> |
| Back | FazBrowse Home | New Git URL |
Uh oh!
There was an error while loading. Please reload this page.