Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}}