đŸ›Ąïž Application Security CheatSheet

Android Content Providers

What a Content Provider is (security perspective)

A Content Provider is Android’s mechanism for exposing structured application data to other apps (or the OS) via content:// URIs. It’s not UI (Activity) and not background execution (Service). It is a local data API, commonly backed by SQLite or internal storage.

From a security standpoint, Content Providers are high-impact because they can bypass UI flows and backend APIs. If exported or weakly protected, other apps on the device can directly read or modify internal data without user interaction and without server-side authorization.

How Content Providers are accessed

Providers are accessed using a URI format:

content://<authority>/<path>

Example:

content://com.bank.app.provider/accounts

Providers typically implement CRUD-style operations:

Web application comparison (why it’s easy to miss)

WebAndroid
Backend API endpointContent Provider
/api/userscontent://com.app.provider/users
GETquery()
POSTinsert()
PUT / PATCHupdate()
DELETEdelete()
Auth middlewarePermissions + caller validation
IDOR / BOLAURI path manipulation
SQL injectionSQLite SQL injection inside provider

The key difference: Content Providers are local. They do not hit your backend. Backend authorization cannot “save you” if your provider is exported and unprotected.

Why Content Providers become vulnerable

Vulnerabilities you see in real apps

1) Unprotected exported provider (direct data exposure)

If a provider is exported and not protected by a strong permission model, any app can query it. This is direct disclosure of app-private data (profiles, cached responses, tokens, messages, transactions, etc.).

2) IDOR / broken object access

If a provider exposes object identifiers via paths (e.g., /accounts/1) and does not validate ownership, an attacker can change the ID and access other objects. This mirrors BOLA in web APIs, but it happens locally.

3) SQL injection inside the provider

Providers often sit directly on SQLite. If selection clauses are built by string concatenation, you can get on-device SQL injection: unexpected rows returned, filters bypassed, or crashes due to malformed SQL.

4) Unauthorized insert/update/delete

If insert/update/delete are exposed without checking who the caller is, other apps can tamper with internal state or delete data. In real apps this can break accounts, corrupt caches, or persist malicious state.

5) Weak permission model / over-broad URI grants

Providers guarded by common/system permissions are often reachable by many unrelated apps. Separately, careless URI grants can give broader access than intended.

How to test Content Providers (practical workflow)

Step 1: Enumerate providers (static)

apktool d app.apk -o out
grep -R "<provider" -n out/AndroidManifest.xml

Step 2: Enumerate providers (runtime)

adb shell dumpsys package com.target.app | sed -n '/Providers:/,/Receivers:/p'

Step 3: Identify exported + permission model

android:exported="true"
android:permission="..."
android:readPermission="..."
android:writePermission="..."

Step 4: Locate provider paths

grep -R "content://" -n out/
grep -R "CONTENT_URI" -n out/

Step 5: Test read access

adb shell content query \
  --uri content://com.target.app.provider/accounts

Step 6: Test IDOR (path manipulation)

adb shell content query \
  --uri content://com.target.app.provider/accounts/1

adb shell content query \
  --uri content://com.target.app.provider/accounts/2

Step 7: Test SQLi (selection probing)

adb shell content query \
  --uri content://com.target.app.provider/accounts \
  --where "1=1--"

Step 8: Test write exposure

adb shell content insert \
  --uri content://com.target.app.provider/accounts \
  --bind name:s:test
adb shell content update \
  --uri content://com.target.app.provider/accounts/1 \
  --bind status:s:inactive
adb shell content delete \
  --uri content://com.target.app.provider/accounts/1

Secure code (vulnerable → fixed) for each issue

1) Unprotected exported provider

Fix: don’t export unless required.

<provider
  android:name=".data.AccountProvider"
  android:authorities="com.example.app.provider"
  android:exported="false" />

If you must share: use signature-level custom permissions.

<permission
  android:name="com.example.app.permission.READ_INTERNAL"
  android:protectionLevel="signature" />

<permission
  android:name="com.example.app.permission.WRITE_INTERNAL"
  android:protectionLevel="signature" />

<provider
  android:name=".data.AccountProvider"
  android:authorities="com.example.app.provider"
  android:exported="true"
  android:readPermission="com.example.app.permission.READ_INTERNAL"
  android:writePermission="com.example.app.permission.WRITE_INTERNAL" />

2) IDOR / broken object access

Vulnerable pattern: returns rows solely based on URI ID.

// Vulnerable (conceptual)
override fun query(uri: Uri, ...): Cursor? {
  val id = ContentUris.parseId(uri)
  return db.query("accounts", null, "id=?", arrayOf(id.toString()), null, null, null)
}

Fix: enforce caller trust + object ownership/authorization before returning data.

// Safer pattern (conceptual)
override fun query(uri: Uri, ...): Cursor? {
  if (!enforceTrustedCaller()) throw SecurityException("Caller not allowed")

  val id = ContentUris.parseId(uri)
  if (!isAccountOwnedByCaller(id)) throw SecurityException("Not authorized")

  return db.query("accounts", null, "id=?", arrayOf(id.toString()), null, null, null)
}

3) SQL injection inside provider

Vulnerable: concatenating untrusted input into SQL selection.

// Vulnerable
val where = "name = '" + userInput + "'"
return db.query("users", null, where, null, null, null, null)

Fix: parameterize selection arguments.

// Safe
val where = "name = ?"
val args = arrayOf(userInput)
return db.query("users", null, where, args, null, null, null)

Also avoid forwarding arbitrary selection/args from external callers. If you need filtering, implement allowlisted filters yourself.

4) Unauthorized insert/update/delete

Vulnerable: write operations without access control.

// Vulnerable
override fun insert(uri: Uri, values: ContentValues?): Uri? {
  val id = db.insert("accounts", null, values)
  return ContentUris.withAppendedId(uri, id)
}

Fix: restrict writes to trusted callers and validate input.

// Safer (conceptual)
override fun insert(uri: Uri, values: ContentValues?): Uri? {
  if (!enforceTrustedCaller()) throw SecurityException("Write not allowed")

  val safe = sanitizeAndValidate(values)
  val id = db.insert("accounts", null, safe)
  return ContentUris.withAppendedId(uri, id)
}

Design fix: if external sharing is only for reads, make the provider read-only.

override fun insert(uri: Uri, values: ContentValues?) =
  throw UnsupportedOperationException("Insert not supported")

override fun update(uri: Uri, values: ContentValues?, where: String?, args: Array<String>?) =
  throw UnsupportedOperationException("Update not supported")

override fun delete(uri: Uri, where: String?, args: Array<String>?) =
  throw UnsupportedOperationException("Delete not supported")

5) Weak permissions / URI grants

Fix: use signature-level permissions for private provider sharing and keep URI grants scoped to the minimum required URI.

<permission
  android:name="com.example.app.permission.READ_INTERNAL"
  android:protectionLevel="signature" />
// If you grant URI access, grant only the specific URI you intend to share
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

Interview questions & answers (Easy → Hard → Senior)

Easy

  1. What is a Content Provider?
    Answer: A component that exposes structured app data via content:// URIs to other apps or the OS.
  2. How is it different from an Activity?
    Answer: Activities expose UI screens; Content Providers expose data directly without UI.
  3. What does a content URI look like?
    Answer: content://<authority>/<path>

Medium

  1. Why are Content Providers high risk?
    Answer: If exported or weakly protected, any app can directly read/modify internal data without backend/UI checks.
  2. How do you test a Content Provider?
    Answer: Enumerate providers (manifest/dumpsys), identify exported/permissions, then use adb shell content to test query/insert/update/delete and prove impact.
  3. What is IDOR in Content Providers?
    Answer: When changing an object ID in the content URI gives access to other users’/objects’ records without authorization.

Hard

  1. Can SQL injection occur inside a Content Provider?
    Answer: Yes. Providers commonly use SQLite; unsafe selection handling can cause on-device SQL injection.
  2. Backend auth is strong—why is an exported provider still a problem?
    Answer: Because providers bypass the backend entirely; the backend is not in the loop.
  3. Is “exported provider” always a vulnerability?
    Answer: No. It’s a vulnerability when sensitive data or operations are reachable without strong permission and access control.

Senior / Architect

  1. How would you design a secure Content Provider?
    Answer: Don’t export unless required, enforce signature-level custom permissions, validate callers where appropriate, parameterize SQL, and minimize exposed data and write operations.
  2. What is a realistic attack scenario?
    Answer: A malicious app enumerates exported providers and silently reads or manipulates sensitive data via ContentResolver.
  3. How do you explain provider risks to a backend team?
    Answer: It’s like a local unauthenticated API that exposes database access and bypasses backend authorization.
← Back: Android Broadcast Receivers Next: Mobile Security →