đŸ›Ąïž Application Security CheatSheet

Android IPC & Intent Abuse

What IPC & intents are (security perspective)

Android IPC is how components communicate across process boundaries. In practice, IPC vulnerabilities happen when an app treats inter-process messages as trusted internal function calls. Intents, PendingIntents, binders, and content URIs are all ways for other apps (or the system) to inject data into your logic.

Most real findings are not exotic: they’re confused deputy issues (your app is tricked into doing something on behalf of an attacker app), parameter trust issues (extras/URIs control privileged actions), or routing issues (untrusted navigation decisions). IPC is the glue layer that turns “exported component” into “exploitable behavior”.

Web application comparison (clear mapping)

Web / APIAndroid IPC
Controller route + query/bodyIntent action/data/extras
Internal API endpointBinder / bound service interface
Redirect parameter abuseUntrusted “target/next/url” routing via intents
CSRF / forged state changeForged broadcasts / intent-driven actions without verification
Signed webhook / shared secretSignature permissions / caller UID verification / explicit intents
OAuth redirect_uri allowlistDeep link / target allowlist + strict URI parsing
Privilege escalation via backendConfused deputy via app privileges (files, content URIs, private APIs)

The right mental model: any exported IPC surface is a public endpoint. If an external app can influence it, you must authenticate/authorize the request and validate inputs like you would for a public API.

Why IPC becomes vulnerable

  1. Implicit resolution: implicit intents and broad filters allow unintended recipients or unintended callers.
  2. Extras-as-authority: code treats extras (IDs, modes, flags) as authoritative rather than advisory.
  3. PendingIntent misconceptions: developers assume PendingIntent is “safe by default”, then ship mutable or reusable tokens.
  4. Caller identity not enforced: components accept calls without checking permissions, calling UID, or package identity.
  5. Lifecycle / state: flows assume “user must have done step X” but IPC can jump directly to step Y.
  6. Data sharing shortcuts: apps share data via intents/broadcasts instead of using explicit recipients or safe contracts.

Vulnerabilities you actually see in real assessments

  1. Implicit intent interception (data leakage)
    Web equivalent: sending sensitive response to attacker-controlled callback

    App sends sensitive extras using an implicit intent. Any third-party app can register an intent-filter and receive it.

  2. Intent spoofing / extra poisoning (confused deputy)
    Web equivalent: forged request hitting privileged internal route

    Exported components accept intents that trigger privileged actions. Attackers spoof actions/extras to invoke logic out of sequence.

  3. Untrusted routing / “next/target/url” navigation
    Web equivalent: open redirect / untrusted post-login redirect

    Router Activities/services accept a “target” or “url” parameter and navigate or load content without strict allowlisting.

  4. PendingIntent hijacking (mutable or over-broad)
    Web equivalent: bearer token allowing arbitrary actions

    A mutable PendingIntent or poorly-scoped PendingIntent allows other apps to modify extras or trigger unintended actions.

  5. Result leakage (setResult / ordered broadcasts)
    Web equivalent: sensitive response returned to untrusted client

    Components return sensitive data in activity results or ordered broadcast result extras to untrusted initiators.

  6. URI permission mishandling (grants too broad)
    Web equivalent: temporary signed URL grants access to the wrong object

    Apps grant read/write access to content:// URIs too broadly or for too long, allowing other apps to access data.

How attackers exploit IPC (typical chains)

In the field, the winning approach is to stop thinking “single component bug” and start thinking “message-driven chain”:

  1. Find a reachable entry point (exported Activity/Receiver/Service, deep link, or implicit intent surface).
  2. Control the inputs (extras, URI, action, flags, PendingIntent fill-in extras).
  3. Force a privileged sink (data access, navigation to sensitive screens, file export, network calls, session state changes).
  4. Confirm it survives real app state (logged out, background, process death, post-login continuation).

Most “good” IPC findings are exploitable without root and without complex instrumentation — just careful reachability and input control.

Practical testing workflow (adb + reproducible steps)

1) Inventory externally reachable IPC surfaces

adb shell dumpsys package com.yourapp | sed -n '/Activities:/,/Services:/p'
adb shell dumpsys package com.yourapp | sed -n '/Services:/,/Receivers:/p'
adb shell dumpsys package com.yourapp | sed -n '/Receivers:/,/Providers:/p'

Extract high-signal intent-filters (deep links and custom actions):

adb shell dumpsys package com.yourapp | grep -n "BROWSABLE\|action.VIEW\|category.BROWSABLE"

2) Test intent spoofing against exported Activities

Direct launch:

adb shell am start -n com.yourapp/.ui.SomeActivity

Launch with extras (tamper the parameters that control object selection or flow):

adb shell am start -n com.yourapp/.ui.AccountActivity --es accountId "12345"
adb shell am start -n com.yourapp/.ui.RouterActivity --es target "settings"
adb shell am start -n com.yourapp/.ui.RouterActivity --es url "https://example.com/"

3) Triage implicit intent interception risk

If your app sends implicit broadcasts/intents containing sensitive extras, it’s a real risk. One fast way to confirm whether intents are explicit or implicit is to observe intent dispatch at runtime.

frida -U -f com.yourapp -l observe_ipc.js --no-pause
// observe_ipc.js (public-safe: logs action + component/package only)
Java.perform(function () {
  var Ctx = Java.use("android.content.ContextWrapper");

  function logIntent(tag, i) {
    try {
      var a = i.getAction();
      var c = i.getComponent();
      var p = i.getPackage();
      console.log(tag + " action=" + a + " component=" + (c ? c.flattenToShortString() : "null") + " pkg=" + (p ? p : "null"));
    } catch (e) {}
  }

  Ctx.sendBroadcast.overload("android.content.Intent").implementation = function (i) {
    logIntent("[sendBroadcast]", i);
    return this.sendBroadcast(i);
  };

  Ctx.startActivity.overload("android.content.Intent").implementation = function (i) {
    logIntent("[startActivity]", i);
    return this.startActivity(i);
  };

  Ctx.startService.overload("android.content.Intent").implementation = function (i) {
    logIntent("[startService]", i);
    return this.startService(i);
  };

  Ctx.sendOrderedBroadcast.overload("android.content.Intent", "java.lang.String").implementation = function (i, perm) {
    logIntent("[sendOrderedBroadcast perm=" + perm + "]", i);
    return this.sendOrderedBroadcast(i, perm);
  };
});

4) Test broadcast spoofing (where applicable)

adb shell am broadcast -n com.yourapp/.receiver.SomeReceiver -a com.yourapp.ACTION_SOMETHING --es mode "test"

5) Test deep links / intent-filters

adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "yourapp://router?target=profile"
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://app.example.com/account/12345"

6) Result leakage triage

rg -n "setResult\(|setResultData\(|setResultExtras\(" jadx-out
rg -n "sendOrderedBroadcast\(" jadx-out

7) PendingIntent safety triage

rg -n "PendingIntent\.getActivity\(|PendingIntent\.getService\(|PendingIntent\.getBroadcast\(" jadx-out
rg -n "FLAG_IMMUTABLE|FLAG_MUTABLE" jadx-out

Secure code patterns (vulnerable → fixed) for each issue

1) Implicit intent interception (data leakage)

Vulnerable (implicit, no recipient restriction):

// Kotlin
val i = Intent("com.yourapp.ACTION_SHARE_STATE")
i.putExtra("userId", userId)
i.putExtra("email", email)
sendBroadcast(i)

Fixed (explicit recipient + keep payload minimal):

// Kotlin
val i = Intent("com.yourapp.ACTION_SHARE_STATE")
i.setPackage(packageName)
i.putExtra("state", "ready")
sendBroadcast(i)

2) Intent spoofing / extra poisoning into exported Activity

Vulnerable (extras decide privileged behavior):

// Kotlin
class RouterActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val target = intent.getStringExtra("target") ?: "home"
    Navigator.go(this, target)
    finish()
  }
}

Fixed (allowlist + enforce auth context):

// Kotlin
class RouterActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val target = intent.getStringExtra("target") ?: "home"
    val allowed = setOf("home", "profile", "settings")
    if (target !in allowed) { finish(); return }

    if (target == "settings" && !SessionManager.isLoggedIn()) {
      startActivity(Intent(this, LoginActivity::class.java))
      finish()
      return
    }

    Navigator.go(this, target)
    finish()
  }
}

3) Untrusted URL routing

Vulnerable:

// Kotlin
val url = intent.getStringExtra("url") ?: return
webView.loadUrl(url)

Fixed (scheme + host allowlist):

// Kotlin
val url = intent.getStringExtra("url") ?: return
val u = android.net.Uri.parse(url)
if (u.scheme != "https") return
if (u.host !in setOf("app.example.com", "help.example.com")) return
webView.loadUrl(url)

4) PendingIntent hijacking (mutable / over-broad)

Vulnerable:

// Kotlin
val i = Intent(this, ConfirmActivity::class.java)
i.putExtra("action", "transfer")
val pi = PendingIntent.getActivity(this, 0, i, 0)

Fixed (immutable + narrow intent + unique requestCode):

// Kotlin
val i = Intent(this, ConfirmActivity::class.java).apply {
  action = "com.yourapp.CONFIRM"
  putExtra("flow", "transfer")
}
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
val pi = PendingIntent.getActivity(this, 1001, i, flags)

5) Result leakage

Vulnerable:

// Kotlin
val result = Intent().putExtra("token", tokenValue)
setResult(Activity.RESULT_OK, result)
finish()

Fixed (avoid returning secrets; verify caller if integration is intended):

// Kotlin
val caller = callingActivity?.packageName
if (caller != packageName) {
  setResult(Activity.RESULT_CANCELED)
  finish()
  return
}
val result = Intent().putExtra("ok", true)
setResult(Activity.RESULT_OK, result)
finish()

6) URI permission grants too broad

Vulnerable:

// Kotlin
val i = Intent(Intent.ACTION_SEND)
i.type = "application/pdf"
i.putExtra(Intent.EXTRA_STREAM, uri)
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(Intent.createChooser(i, "Share"))

Fixed (scope sharing intentionally; prefer explicit targets when possible):

// Kotlin
val i = Intent(Intent.ACTION_SEND).apply {
  type = "application/pdf"
  putExtra(Intent.EXTRA_STREAM, uri)
  addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
  setPackage("com.trusted.viewer")
}
startActivity(i)

If you must use a chooser, ensure the provider paths are scoped and access checks are correct. The real control lives in your provider rules.

Interview questions & answers (Easy → Medium → Hard → Senior)

Easy

  1. What’s an Intent in Android security terms?

    A message envelope used to invoke components or actions. Security-wise, it’s untrusted input if it can originate outside your app.

  2. What’s the difference between explicit and implicit intents?

    Explicit intents name the target component/package. Implicit intents rely on intent-filter resolution and can be intercepted or matched by other apps.

  3. What’s the most common IPC bug you see?

    Trusting extras/URI parameters to select objects or trigger privileged actions without enforcing caller identity and user/session context.

Medium

  1. Why are implicit broadcasts/intents risky?

    Any app can register an intent-filter and become a recipient. If your payload contains sensitive data, you’ve created a data exfil channel.

  2. What’s a confused deputy in Android IPC?

    When your app has privileges and an attacker app tricks it via IPC inputs into performing actions on the attacker’s behalf.

  3. How do you triage PendingIntent risk quickly?

    Find creation sites, check immutability/mutability flags, confirm least-privilege intent scoping, and check for unintended reuse across contexts.

Hard

  1. Is “not exported” enough to make an IPC surface safe?

    It prevents direct cross-app calls, but you still need to consider indirect reachability (exported receiver starting a non-exported service) and authorization at the sink.

  2. What’s your approach to validating an “auth gate” on entry points?

    I test reachability logged out and logged in, then verify whether the app preserves attacker-controlled intents after login. Deep link → login → resume original intent is a common real flaw when parameters aren’t revalidated.

  3. How do you decide whether an IPC finding is “real”?

    I require a chain: reachable entry point, attacker-controlled input, privileged sink, and observable impact. If any part is assumption-based, I validate with runtime observation or a controlled test harness.

Senior

  1. How do you reduce IPC risk at an app-wide level?

    Prefer explicit intents, minimize exported components, use signature permissions for internal IPC, validate caller identity for binder surfaces, treat routing params as untrusted with strict allowlists, and keep secrets out of intents/broadcasts. Also: make post-login continuation sanitized by design.

  2. Where do teams usually get PendingIntent wrong?

    They treat it as a notification hook and forget it’s a capability token. If it’s mutable, broadly scoped, or reusable, it becomes an action primitive. Fix is immutability + least-privilege intent + careful requestCode usage.

  3. What’s your highest-signal static review focus for IPC?

    Central routers/dispatchers and IPC glue: intent builders, deep link handlers, PendingIntent creation, and binder stubs — where internal assumptions become external control.