From c7aa1f758e0620e46f7dee4b503b805eacb52591 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sun, 20 Sep 2026 01:30:25 +0000 Subject: [PATCH] =?UTF-8?q?Add=20content=20from:=20CVE-2026-28576:=20One?= =?UTF-8?q?=20Picked=20Contact,=20Every=20Contact=E2=80=94SQL=20Inject...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../content-protocol.md | 27 +++++++++++++++++++ .../exploiting-content-providers.md | 3 +++ 2 files changed, 30 insertions(+) diff --git a/src/mobile-pentesting/android-app-pentesting/content-protocol.md b/src/mobile-pentesting/android-app-pentesting/content-protocol.md index 0992c1f1d47..59e70a72b1b 100644 --- a/src/mobile-pentesting/android-app-pentesting/content-protocol.md +++ b/src/mobile-pentesting/android-app-pentesting/content-protocol.md @@ -155,10 +155,37 @@ On modern Android versions, direct `/sdcard` paths matter less than **granted** From an offensive perspective, these URIs are attractive because a vulnerable app may call `takePersistableUriPermission()` after receiving them. That turns a one-shot `content://` access into a **long-lived grant** that can survive app restarts or even device reboots. When chaining an exported proxy Activity or an intent-redirection bug, prefer testing **document/tree URIs** in addition to plain MediaStore paths. +#### Scoped URI grants as SQL-injection carriers + +A narrowly scoped read grant can pass the provider's URI authorization while a caller-controlled `selection` expression reads outside the authorized row. Consequently, querying another URI may correctly throw `SecurityException`, yet the granted URI can still act as an SQL-injection carrier. CVE-2026-28576 demonstrated this boundary mismatch in Android 17's Contacts Provider: a picker-issued grant for one contact was enough to infer data from the underlying contacts database without `READ_CONTACTS`.[[4]](#references)[[7]](#references) + +In that case, `ContactsProvider2.queryLocal()` enabled `setStrictColumns()` and `setStrictGrammar()` only when compat change `484953293` applied to the calling UID. The change was annotated with `@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)`, so an application targeting SDK 36 or lower stayed on the permissive path. The existing `setStrict(true)` wrapping defeated unbalanced clause breakouts, but a syntactically balanced scalar subquery remained valid.[[5]](#references)[[7]](#references) + +The following probe requests only the granted row's `_id`; the sensitive value influences whether that outer row is returned. A true guess produces a non-empty cursor and a false guess produces an empty cursor.[[7]](#references) + +```java +String probe = "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'"; +try (Cursor c = getContentResolver().query( + grantedUri, new String[]{"_id"}, probe, null, null)) { + boolean match = c != null && c.getCount() > 0; +} +``` + +To turn this into a Boolean extraction oracle, first establish the normal row count for the granted URI, then vary the `substr()` position and guessed character. Advance `LIMIT 1 OFFSET k` to enumerate rows and change the table/filter to select other fields. This technique is especially useful when error text is hidden and projection injection is blocked, because only cursor cardinality is observed.[[7]](#references) + +During review, test both current and legacy `targetSdkVersion` values and inspect every compat-gated validation branch. On a vulnerable test build, the fixed behavior can be simulated for the probe package with `adb shell am compat enable 484953293 `; strict grammar rejects the nested `SELECT` with `IllegalArgumentException` before SQLite executes it. `SQLiteQueryBuilder.setStrictGrammar(true)` rejects subqueries in `WHERE`/`HAVING`, but it is disabled by default on a new builder, so providers must enable it (and strict column validation) for every untrusted caller rather than only for new-target applications.[[5]](#references)[[6]](#references)[[7]](#references) + ## References - [1] [CENSUS - Remote exploitation of a man-in-the-disk vulnerability in WhatsApp (CVE-2021-24027)](https://www.census-labs.com/resources/remote-exploitation-of-a-man-in-the-disk-vulnerability-in-whatsapp-cve-2021-24027) - [2] [Microsoft - "Dirty stream" attack: Discovering and mitigating a common vulnerability pattern in Android apps](https://www.microsoft.com/en-us/security/blog/2024/05/01/dirty-stream-attack-discovering-and-mitigating-a-common-vulnerability-pattern-in-android-apps/) - [3] [census-labs.com - Whatsapp Mitd Remote Exploitation CVE 2021 24027](https://census-labs.com/news/2021/04/14/whatsapp-mitd-remote-exploitation-CVE-2021-24027) +- [4] [Android 17 Security Release Notes](https://source.android.com/docs/security/bulletin/android-17) +- [5] [GrapheneOS - Always enforce strict SQL checks regardless of app targetSdk](https://github.com/GrapheneOS/platform_packages_providers_ContactsProvider/commit/c4129a1c210f) +- [6] [Android SDK - SQLiteQueryBuilder.setStrictGrammar()](https://developer.android.com/reference/android/database/sqlite/SQLiteQueryBuilder#setStrictGrammar(boolean)) +- [7] [Mobile Hacking Lab - CVE-2026-28576 analysis and PoC](https://github.com/mobilehackinglab/CVE-2026-28576-poc) {{#include ../../banners/hacktricks-training.md}} diff --git a/src/mobile-pentesting/android-app-pentesting/drozer-tutorial/exploiting-content-providers.md b/src/mobile-pentesting/android-app-pentesting/drozer-tutorial/exploiting-content-providers.md index c6a54018bfd..5650049f441 100644 --- a/src/mobile-pentesting/android-app-pentesting/drozer-tutorial/exploiting-content-providers.md +++ b/src/mobile-pentesting/android-app-pentesting/drozer-tutorial/exploiting-content-providers.md @@ -157,6 +157,8 @@ Accessible tables for uri content://jakhar.aseem.diva.provider.notesprovider/not sqlite_sequence ``` +For the distinct case where the caller holds only a row-scoped URI grant, continue with [**Scoped URI grants as SQL-injection carriers**](../content-protocol.md#scoped-uri-grants-as-sql-injection-carriers).[[12]](#references) + ### writePermission omission + blind SQLi via update() A common OEM mistake is to export a ContentProvider with a readPermission but omit writePermission. When writePermission is null, any app can call insert/update/delete if those methods are implemented. If update() concatenates the caller-controlled WHERE (selection) directly into an SQL statement, you can build a blind inference oracle and exfiltrate data from other tables in the same SQLite DB (even those normally protected by privileged read permissions like READ_SMS).[[1]](#references)[[2]](#references) @@ -417,5 +419,6 @@ These changes in recent Android versions mean many legacy exploitation primitive - [9] [drozer 3.1.0 release notes](https://github.com/WithSecureLabs/drozer/releases/tag/3.1.0) - [10] [Android Security Bulletin—July 2024](https://source.android.com/security/bulletin/2024-07-01) - [11] [Reading Contact Photos Without READ_CONTACTS: A Google Messages Confused Deputy Bug](https://blog.devploit.dev/posts/google-messages-avatarcontentprovider-contacts-bypass/) +- [12] [Mobile Hacking Lab - CVE-2026-28576 analysis and PoC](https://github.com/mobilehackinglab/CVE-2026-28576-poc) {{#include ../../../banners/hacktricks-training.md}}