đŸ›Ąïž Application Security CheatSheet

Android Activities

What an Activity is (security perspective)

An Activity is an Android component that represents a user-facing entry point: a screen, a flow step, or (in many apps) a “router” that receives an Intent, parses parameters, and triggers privileged logic (load account data, start a payment flow, show an admin UI, etc.).

From an AppSec perspective, Activities are dangerous when teams assume “it’s only reachable via our UI”. That assumption collapses the moment an Activity is exported, has a broad intent-filter (deep links / share targets), or is reachable through a confused flow (task behavior, reparenting, singleTask/singleInstance quirks). In assessments, Activities are where you find broken access control, intent parameter tampering, phishable deep-link entry points, and logic you can invoke out of sequence.

How Activities are accessed

Activities are launched via Intents. The launch surface is bigger than it looks:

Practical implication: if an Activity can be launched by anything other than your own code path, treat every input (data, extras, flags, calling identity) as attacker-controlled.

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

WebAndroid
Route / controllerActivity (screen/controller boundary)
URL path + query parametersDeep link URI (intent.data) + extras
Auth middleware / route guardSession gate in onCreate/onResume + navigation enforcement
IDOR via /users/123IDOR via userId / accountId extras or deep link path
Open redirect / untrusted redirect paramUntrusted “next/target/url” extras in router Activities
CSRF-ish “state change via GET”State change triggered on launch (no user confirmation / no context checks)
ClickjackingTapjacking / UI overlay risks (not Activity-only, but Activity flags matter)

The mental model that works: an exported Activity is a public endpoint. If it performs privileged work on entry (or trusts parameters to decide what to show/do), it needs the same rigor as a web controller.

Why Activities become vulnerable

  1. Exported surface grows quietly: Android 12+ forces explicit android:exported on components with intent-filters, but teams often set it to true “to make deep links work” and don’t re-audit the Activity’s behavior.
  2. UI-path assumptions: developers assume sensitive screens are only reachable after login or after certain steps, then skip checks inside the Activity itself.
  3. Parameter-driven navigation: “router” Activities parse extras like target, screen, url, accountId and branch into privileged flows without strict validation.
  4. Lifecycle complexity: state may be restored via back stack, recents, rotation, process death, and deep links. If the security gate is only in one place (or relies on in-memory flags), it’s brittle.
  5. Task & launch mode foot-guns: misconfigured taskAffinity, launchMode, or reparenting can make phishing/flow-confusion easier than teams expect.

Vulnerabilities you see in real apps

  1. Exported Activity exposes an internal screen
    Web equivalent: unprotected route / missing auth middleware

    The Activity opens account details, admin/debug screens, or sensitive operations without enforcing login/authorization inside the Activity.

  2. Intent parameter tampering (IDOR / BOLA via extras or deep link paths)
    Web equivalent: IDOR via URL parameter (object reference)

    The Activity trusts userId, accountId, docId from extras/URI and loads data or triggers actions. If backend checks are weak (or calls are local), you can pivot across users.

  3. Untrusted navigation / redirect inside router Activities
    Web equivalent: open redirect / untrusted post-login redirect

    Apps implement “deep link router” Activities that accept next/target/url and forward users to screens or web content. Without allowlisting and context checks, it becomes a phishing primitive and sometimes a data-leak primitive.

  4. Result leakage to untrusted callers
    Web equivalent: sensitive data in cross-origin response / insecure callback

    An exported Activity returns sensitive data via setResult() (tokens, identifiers, personal data) to whoever launched it, or exposes data in Intent results without caller verification.

  5. Task/flow confusion (phishable login or sensitive confirmation screens)
    Web equivalent: UI redress / login CSRF-ish flow manipulation

    Misconfigurations (task affinity, reparenting, permissive deep link filters) enable attackers to push users into confusing contexts: screens that look legitimate but are entered through attacker-controlled paths.

  6. Crash/DoS via malformed intents
    Web equivalent: endpoint crashes on bad input

    Exported Activities that assume required extras exist can be crashed repeatedly, degrading availability or forcing users into re-auth loops.

How to test Activities (practical workflow)

The goal is to build an inventory of reachable Activities, identify which ones are externally launchable, then probe: (1) auth/authorization enforcement, (2) parameter trust, (3) navigation/redirect behavior, (4) lifecycle and task quirks.

1) Enumerate Activities and exported surface

On a device/emulator with the app installed:

adb shell pm list packages | grep -i yourapp
adb shell dumpsys package com.yourapp | sed -n '/Activities:/,/Services:/p'

Quickly list intent-filters (deep links) and exported flags:

adb shell dumpsys package com.yourapp | grep -n "Activity" -n
adb shell dumpsys package com.yourapp | grep -n "intent-filter" -n

Static cross-check (faster for large apps):

# pull manifest (one approach)
adb shell pm path com.yourapp
adb pull /data/app/<...>/base.apk

# decode manifest
apktool d -f base.apk -o out
grep -R "activity" -n out/AndroidManifest.xml

2) Try launching exported Activities directly

Explicit launch:

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

Launch with extras (common tampering vector):

adb shell am start -n com.yourapp/.ui.AccountActivity --es accountId "12345"
adb shell am start -n com.yourapp/.ui.DocumentActivity --es docId "A-1001"

3) Exercise deep links / intent-filters

For an https:// App Link / deep link:

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

Look for:

4) Observe task and activity state

See current tasks / stacks to understand launchMode/task behaviors:

adb shell dumpsys activity activities | sed -n '/Running activities/,/Recent tasks/p'

5) Dynamic inspection (when inputs disappear or are rewritten)

If the app normalizes/rewrites intents (or you suspect guards in lifecycle callbacks), instrument to observe what the Activity actually receives.

# example: start app with Frida to observe intent extras in Activity.onCreate
frida -U -f com.yourapp -l observe_intents.js --no-pause

A minimal, public-safe observation script (logs keys only, not secrets):

// observe_intents.js
Java.perform(function () {
  var Activity = Java.use("android.app.Activity");
  Activity.onCreate.overload("android.os.Bundle").implementation = function (b) {
    var intent = this.getIntent();
    if (intent) {
      var extras = intent.getExtras();
      if (extras) {
        var keys = extras.keySet().toArray();
        console.log("[Activity] " + this.getClass().getName() + " extras keys: " + keys);
      }
      var data = intent.getDataString();
      if (data) console.log("[Activity] " + this.getClass().getName() + " data: " + data);
    }
    return this.onCreate(b);
  };
});

6) Validate whether “guards” are real or cosmetic

If a sensitive Activity redirects you to login, confirm what happens after login: does it return to the original Activity with attacker-controlled parameters intact?

In real apps, the vulnerable pattern is: deep link → login → app resumes original intent → privileged action happens.

Secure code (vulnerable → fixed) for each issue

1) Exported Activity without an internal auth gate

Vulnerable (Manifest + Activity):

<activity
  android:name=".ui.AccountActivity"
  android:exported="true" />
// Kotlin
class AccountActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Assumes caller is already authenticated because "UI flow"
    showAccountScreen()
  }
}

Fixed (prefer reducing surface, then enforce gate):

<activity
  android:name=".ui.AccountActivity"
  android:exported="false" />
// Kotlin
class AccountActivity : AppCompatActivity() {
  override fun onResume() {
    super.onResume()
    if (!SessionManager.isLoggedIn()) {
      startActivity(Intent(this, LoginActivity::class.java))
      finish()
      return
    }
    showAccountScreen()
  }
}

2) IDOR/BOLA via extras or deep link parameters

Vulnerable:

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

  val accountId = intent.getStringExtra("accountId") ?: return
  // Bad: treating accountId from the Intent as authoritative
  api.getAccount(accountId) { render(it) }
}

Fixed (never trust “which object” from the client; bind to session):

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

  if (!SessionManager.isLoggedIn()) { finish(); return }

  // Good: server decides which accounts this user can access
  val requested = intent.getStringExtra("accountId")
  api.getAccountForUser(requested, SessionManager.userId()) { result ->
    if (result.isUnauthorized) { finish(); return }
    render(result.account)
  }
}

Note: the critical fix is enforcement on the backend/data layer. The Activity fix is to avoid “trusting the ID” and to handle unauthorized outcomes consistently.

3) Untrusted navigation / “router” extras (next/target/url)

Vulnerable:

// Kotlin
val target = intent.getStringExtra("target") ?: "home"
Navigator.go(this, target) // target controls where the user lands

Fixed (tight allowlist + context checks):

// Kotlin
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)

4) Result leakage to untrusted callers

Vulnerable:

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

Fixed (restrict export + verify caller if a result is required):

<activity
  android:name=".ui.ExportTokenActivity"
  android:exported="false" />
// Kotlin
val caller = callingActivity?.packageName
if (caller != "com.yourapp") { // adjust if you truly expect external callers
  setResult(Activity.RESULT_CANCELED)
  finish()
  return
}
val result = Intent().putExtra("token", tokenValue)
setResult(Activity.RESULT_OK, result)
finish()

If you actually support external apps, prefer a signed/permissioned integration rather than returning sensitive values to arbitrary callers.

5) Task/flow confusion via task settings

Vulnerable (manifest patterns that often contribute):

<activity
  android:name=".ui.LoginActivity"
  android:exported="true"
  android:taskAffinity="com.yourapp.login"
  android:allowTaskReparenting="true" />

Fixed (keep tasks predictable, reduce special cases):

<activity
  android:name=".ui.LoginActivity"
  android:exported="false"
  android:taskAffinity=""
  android:allowTaskReparenting="false" />

This is not “one magic manifest flag”. The point is to avoid creating alternate tasks for sensitive flows unless you have a clear, tested reason.

6) Crash/DoS on malformed intents

Vulnerable:

// Kotlin
val id = intent.getStringExtra("id")!!  // crashes if missing
render(load(id))

Fixed (validate and fail closed):

// Kotlin
val id = intent.getStringExtra("id")
if (id.isNullOrBlank()) { finish(); return }

val obj = loadOrNull(id)
if (obj == null) { finish(); return }

render(obj)

Interview questions & answers (Easy → Hard → Senior)

Easy

  1. What does android:exported mean for an Activity?

    It controls whether other apps (and the system, via intent resolution) can launch the Activity. If it’s exported, treat the Activity like a public endpoint: intents, extras, and deep link data are untrusted input.

  2. What’s the web equivalent of an exported Activity?

    A publicly reachable route/controller. If it performs privileged work, it needs an internal auth/authorization gate just like a web endpoint would.

  3. How would you quickly test if an Activity is externally launchable?

    Enumerate with dumpsys package or the manifest, then attempt an explicit launch via adb shell am start -n package/.Activity. If it opens outside the normal UI flow, it’s reachable.

Medium

  1. What’s the most common real vulnerability in Activities?

    Broken access control through UI-path assumptions: sensitive screens or actions that don’t enforce session/authorization inside the Activity, combined with exported status or broad deep links.

  2. How do you think about IDOR in mobile Activities?

    Exactly like web IDOR: if an Activity trusts an object identifier from the client (extras or deep link) to fetch or mutate an object, you test whether changing that identifier crosses tenant/user boundaries. The real fix is server/data-layer authorization.

  3. What do you look for in a “deep link router” Activity?

    Any parameter that influences navigation (target, screen, next, url), and whether the router enforces allowlists and authentication context before forwarding. Also whether login preserves attacker-controlled intents.

Hard

  1. An Activity checks login in onCreate(). Is that sufficient?

    Not always. Lifecycle matters: the Activity can be resumed from background, restored after process death, or revisited via recents. Guards that only run in onCreate() can be bypassed if state changes after creation. A common pattern is enforcing in onResume() and ensuring sensitive actions are gated at the moment of execution, not only at construction time.

  2. How do you distinguish “cosmetic” navigation checks from real authorization?

    Cosmetic checks prevent the screen from rendering but don’t stop the underlying action. I validate whether privileged requests are still possible (or objects are still accessible) by changing intent parameters, replaying deep links post-login, and verifying backend responses. If the backend authorizes correctly, the UI check is a usability layer, not the security control.

  3. What’s a realistic risk of returning results from an Activity?

    If the Activity is exported or callable by other apps, setResult() can become a data exfil channel. It’s the same idea as exposing a sensitive API response cross-origin. Restrict the surface and verify the caller for integrations.

Senior

  1. How do you systematically audit Activities across a large Android app?

    I start with an inventory: manifest export status, intent-filters, deep links, launch modes, and permission gates. Then I map Activities into categories: routers, auth/session, account/data views, admin/debug, external entry points (BROWSABLE/share), and “action on entry” screens (that trigger network/local mutations). I prioritize exported + action-on-entry + parameter-driven. For each, I validate: auth enforcement, object binding to session, safe navigation decisions, and lifecycle resilience (resume/recents).

  2. What’s your guidance to engineering to prevent Activity-level access control bugs?

    Minimize exported surface; treat exported Activities as public endpoints with explicit contracts; centralize session gates in a base Activity or navigation layer but still enforce authorization at the data/backend layer; and make router targets allowlist-driven. Finally, add regression tests for deep link flows (logged-out, logged-in, post-login continuation) because that’s where issues recur.

  3. If a team insists an exported Activity is required for deep links, what do you require before sign-off?

    A narrow intent-filter (verified hosts for App Links, minimal paths), strict parsing with allowlists, an explicit authentication continuation design that does not preserve attacker-controlled intents blindly, and backend/data-layer authorization for any object access. I also require a negative test plan using adb am start for malformed/malicious parameters and a logging strategy that does not leak secrets from intents.