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:
- Explicit intents target a component directly (
com.app/.ui.SomeActivity). - Implicit intents are resolved by intent-filters (actions/categories/data URIs), including deep links.
- Task/recents can revive Activities with stale state (especially if you rely on âprevious screenâ assumptions).
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)
| Web | Android |
|---|---|
| Route / controller | Activity (screen/controller boundary) |
| URL path + query parameters | Deep link URI (intent.data) + extras |
| Auth middleware / route guard | Session gate in onCreate/onResume + navigation enforcement |
IDOR via /users/123 | IDOR via userId / accountId extras or deep link path |
| Open redirect / untrusted redirect param | Untrusted ânext/target/urlâ extras in router Activities |
| CSRF-ish âstate change via GETâ | State change triggered on launch (no user confirmation / no context checks) |
| Clickjacking | Tapjacking / 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
-
Exported surface grows quietly: Android 12+ forces explicit
android:exportedon components with intent-filters, but teams often set it totrueâto make deep links workâ and donât re-audit the Activityâs behavior. - UI-path assumptions: developers assume sensitive screens are only reachable after login or after certain steps, then skip checks inside the Activity itself.
-
Parameter-driven navigation: ârouterâ Activities parse extras like
target,screen,url,accountIdand branch into privileged flows without strict validation. - 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.
-
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
-
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.
-
Intent parameter tampering (IDOR / BOLA via extras or deep link paths)
Web equivalent: IDOR via URL parameter (object reference)
The Activity trusts
userId,accountId,docIdfrom extras/URI and loads data or triggers actions. If backend checks are weak (or calls are local), you can pivot across users. -
Untrusted navigation / redirect inside router Activities
Web equivalent: open redirect / untrusted post-login redirect
Apps implement âdeep link routerâ Activities that accept
next/target/urland forward users to screens or web content. Without allowlisting and context checks, it becomes a phishing primitive and sometimes a data-leak primitive. -
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 inIntentresults without caller verification. -
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.
-
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:
- Does the Activity open while logged out?
- Does it show someone elseâs object when you change IDs?
- Does it accept a ânext/target/urlâ parameter and navigate broadly?
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
-
What does
android:exportedmean 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.
-
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.
-
How would you quickly test if an Activity is externally launchable?
Enumerate with
dumpsys packageor the manifest, then attempt an explicit launch viaadb shell am start -n package/.Activity. If it opens outside the normal UI flow, itâs reachable.
Medium
-
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.
-
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.
-
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
-
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 inonResume()and ensuring sensitive actions are gated at the moment of execution, not only at construction time. -
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.
-
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
-
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).
-
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.
-
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 startfor malformed/malicious parameters and a logging strategy that does not leak secrets from intents.