From 98c03ccfdbcf56075623cbd998d75e88ab3233ed Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Fri, 3 Jul 2026 09:10:57 +0000 Subject: [PATCH] Add content from: Reading Contact Photos Without READ_CONTACTS: A Google Messa... --- .../exploiting-content-providers.md | 70 +++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) 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 894689c0c4e..3acefdd6eb6 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 @@ -283,6 +283,71 @@ Vulnerable Providers: content://com.mwr.example.sieve.FileBackupProvider ``` +## Exported provider as a confused deputy + +An **exported** `ContentProvider` can become a **permission proxy** if it accepts a **caller-controlled backend URI** (for example via a path segment, query parameter, or `call()` bundle), dereferences that URI with the **provider app UID**, and then returns the resulting bytes to the lower-privileged caller. + +Typical bug pattern: + +- Provider is exported and reachable by untrusted apps +- No caller permission, or a weak permission any app can hold +- App has privileged access to another provider (for example Contacts, SMS, media, work-profile data) +- External caller controls a backend `content://` URI that is later passed into `ContentResolver.query/openInputStream/openFileDescriptor/openAssetFileDescriptor` +- Provider returns the original bytes or **derived output** (resized bitmap, transcoded PNG/JPEG, cached file, thumbnail) + +Why this matters: + +- The security boundary is enforced on the **UID performing the read** +- A direct read from the attacker app may fail with `SecurityException` +- The same URI may succeed when the victim provider reads it with its own permissions +- **Rendering or transcoding does not remove the leak**: once protected data is converted to another format and streamed back, the permission boundary is already bypassed + +Minimal triage workflow: + +1. Enumerate exported providers and inspect manifest permissions (`readPermission`, `writePermission`, `grantUriPermissions`, path permissions) +2. Reverse `query()`, `openFile()`, `openAssetFile()`, `openTypedAssetFile()`, and `call()` looking for attacker-controlled URIs +3. Trace sinks such as: + - `Uri.parse(...)` + - `getQueryParameter(...)` + - `Bundle.getString(...)` + - `ContentResolver.query(...)` + - `openInputStream(...)` + - `openFileDescriptor(...)` +4. Test the protected backend URI directly from a no-permission app to confirm it is blocked +5. Send the **same** URI through the exported provider and compare the response + +ADB-style reproduction: + +```bash +# Direct read from the attacker UID should fail +adb shell am start -S -n com.local.probe/.ProbeActivity \ + --es mode query-uri \ + --es uri content://com.android.contacts/contacts/CONTACT_ID/photo + +# The same protected URI wrapped by the exported provider may succeed +adb shell am start -S -n com.local.probe/.ProbeActivity \ + --es mode query-uri \ + --es uri 'content://com.victim.provider/r?m=content%3A%2F%2Fcom.android.contacts%2Fcontacts%2FCONTACT_ID%2Fphoto' +``` + +Common static signals: + +- `android:exported="true"` with no `readPermission` / `writePermission` +- `grantUriPermissions="true"` combined with broad URI handling +- Image/avatar/thumbnail/document preview providers that accept a source URI +- Internal "safe URI" checks that only validate syntax/authority but **do not verify the original caller can access the source** + +Enumeration hint: + +If missing objects return a **stable fallback** (default avatar, placeholder thumbnail, empty file), the bug may be enumerable. Iterate predictable IDs and keep responses whose **size**, **hash**, or **decoded pixels** differ from the fallback. + +Safer design for developers: + +- Do not dereference untrusted external `content://` URIs with the app's ambient permissions +- Prefer serving only app-owned opaque resources +- If callers must provide a URI, require an explicit grant or verify the caller can access the source before reading it +- Make the provider non-exported or protect it with a signature/internal permission unless cross-app access is really needed + ## 2023-2025 Updates & Modern Tips ### Drozer 3.x (Python 3) is out @@ -351,9 +416,6 @@ These changes in recent Android versions mean many legacy exploitation primitive - [https://labs.withsecure.com/content/dam/labs/docs/mwri-drozer-user-guide-2015-03-23.pdf](https://labs.withsecure.com/content/dam/labs/docs/mwri-drozer-user-guide-2015-03-23.pdf) - [https://github.com/WithSecureLabs/drozer/releases/tag/3.1.0](https://github.com/WithSecureLabs/drozer/releases/tag/3.1.0) - [https://source.android.com/security/bulletin/2024-07-01](https://source.android.com/security/bulletin/2024-07-01) +- [Reading Contact Photos Without READ_CONTACTS: A Google Messages Confused Deputy Bug](https://blog.devploit.dev/posts/google-messages-avatarcontentprovider-contacts-bypass/) {{#include ../../../banners/hacktricks-training.md}} - - - -