# SQL injection in exported content providers

Exported ContentProvider passing caller-controlled where, selection, projection or ContentValues keys into SQLite

_Area: Mobile app security. Seen in 2 disclosed reports; 2 confirmed bounties: $100 and $300; latest 2023-04-12. Updated 2026-11-02._

## How it works
An Android ContentProvider is a process boundary any co-located app can cross. When the provider is exported, the caller controls the arguments of query/insert/update/delete: `where`, `selection`, `sortOrder`, `projection`, and the *keys* of `ContentValues`. The broken assumption is that these arguments are data. In SQLite they are partly syntax: a projection is a column list, a where clause is a predicate, and a ContentValues key becomes a column name in the generated INSERT/UPDATE. If the provider concatenates them into SQL instead of parameterizing or validating, the caller writes SQL. The subtle case is the key string, not the value: `col=?,other=(SELECT ...)` injects through the column name while the value stays bound [E1](https://bugbountylibrary.com/r/h1%3A1650264.md).

## Where it shows up
- `android:exported="true"` on a provider in AndroidManifest.xml, especially one backed by a local SQLite file [E3](https://bugbountylibrary.com/r/h1%3A242727.md).
- Provider methods forwarding `selection`, `projection`, `sortOrder` or `ContentValues` keys straight into `db.query/insert/update/delete` [E1](https://bugbountylibrary.com/r/h1%3A1650264.md).
- Sanitization that runs only for one URI case in a switch, leaving sibling cases (SINGLE_FILE, DIRECTORY) unchecked [E2](https://bugbountylibrary.com/r/h1%3A518669.md).
- Caller checks using `contains()`/`startsWith()` on package names instead of exact equality [E4](https://bugbountylibrary.com/r/h1%3A331302.md).
- Local caches holding share tokens, password hashes, keys or account metadata behind that provider [E2](https://bugbountylibrary.com/r/h1%3A518669.md)[E3](https://bugbountylibrary.com/r/h1%3A242727.md).

## How to review
For each exported provider, list every method and trace each caller-supplied argument to the SQLite call. The invariant: no caller-controlled string reaches SQL text unparameterized, and projections/keys are checked against an allowlist of real column names. Verify the authorization check runs on *every* URI branch, not just the root, and that it compares package name or UID exactly. Confirm the data behind the provider is not sensitive enough to matter if the check is bypassed.

## How finders spotted it
- Traced each ContentProvider parameter to raw SQLite calls and noticed ContentValues keys becoming column names [E1](https://bugbountylibrary.com/r/h1%3A1650264.md).
- Read the URI switch, saw SINGLE_FILE/DIRECTORY skip `isCallerNotAllowed`, and that projection sanitization was gated on ROOT_DIRECTORY only [E2](https://bugbountylibrary.com/r/h1%3A518669.md).
- Grepped the manifest for `android:exported="true"`, then queried the authority with `adb content query` and a provider helper app to confirm exposure [E3](https://bugbountylibrary.com/r/h1%3A242727.md).
- Read caller-validation code and saw `contains()` on the package name instead of an exact match [E4](https://bugbountylibrary.com/r/h1%3A331302.md).

## Variants seen
- Injection via where/selection/sortOrder into delete/insert/update/query [E1](https://bugbountylibrary.com/r/h1%3A1650264.md).
- Injection via projection, enabled by a per-URI authorization gap [E2](https://bugbountylibrary.com/r/h1%3A518669.md).
- No injection at all: provider simply exported, exposing stored hashes and tokens [E3](https://bugbountylibrary.com/r/h1%3A242727.md).
- Weak caller authentication by substring package match [E4](https://bugbountylibrary.com/r/h1%3A331302.md).

## Impact and severity
Any installed app could read arbitrary tables: share tokens, owner data, account and file metadata [E1](https://bugbountylibrary.com/r/h1%3A1650264.md)[E2](https://bugbountylibrary.com/r/h1%3A518669.md), and bcrypt password hashes enabling offline cracking [E3](https://bugbountylibrary.com/r/h1%3A242727.md). Weak caller checks exposed E2E private keys [E4](https://bugbountylibrary.com/r/h1%3A331302.md). Severity rises with what the local DB caches and whether the provider is exported by default.

## Why fixes fail
Sanitizing only the root URI left sibling cases open [E2](https://bugbountylibrary.com/r/h1%3A518669.md); substring caller checks are trivially bypassed by a crafted package name [E4](https://bugbountylibrary.com/r/h1%3A331302.md).

## Seen in real software
Open-source packages and projects, and products, where this pattern was found. If the target runs one, compare the version it runs with the fix.
- **ownCloud**: GitHub Security Lab (GHSL) Vulnerability Report: SQLInjection in FileContentProvider.kt (GHSL-2022-059). Report: https://bugbountylibrary.com/r/h1%3A1650264.md
- **Nextcloud**: SQLi allow query restriction bypass on exposed FileContentProvider (CVE-2019-15622). Report: https://bugbountylibrary.com/r/h1%3A518669.md

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A1650264.md
- E2: https://bugbountylibrary.com/r/h1%3A518669.md
- E3: https://bugbountylibrary.com/r/h1%3A242727.md
- E4: https://bugbountylibrary.com/r/h1%3A331302.md
