Android Services
What a Service is (security perspective)
An Android Service is a component meant for non-UI work: background processing, long-running operations, integrations, and (critically) IPC entry points. In real apps, services arenât just âbackground codeâ â they are often the place where teams centralize privileged operations like syncing, token refresh, file exports, analytics pipelines, and device/OS interactions.
Security-wise, a Service can be: started (one-way command), bound (two-way IPC via Binder), or both. A started service is typically a command receiver. A bound service is effectively an in-app API surface. If itâs exported or otherwise reachable, treat it as a remote API with attacker-controlled inputs, concurrency, and calling identity concerns.
How Services are reached
- Started service:
startService()/startForegroundService()triggersonStartCommand(). - Bound service:
bindService()exposes anIBinderthat other processes can call. - JobScheduler / WorkManager: not Services per se, but services often back these flows and inherit similar trust issues.
- Indirect triggers: Activities/Receivers start services; exported upstream components can make the service reachable.
In assessments, the highest-risk pattern is a service that performs privileged actions based on intent extras or binder parameters without verifying caller identity, permissions, or user/session context.
Web application comparison (clear mapping)
| Web | Android |
|---|---|
| Internal API endpoint | Bound service (Binder interface) |
| Command endpoint / queue consumer | Started service (intent-driven command handler) |
| Authentication middleware | Permission gate + calling UID validation + session binding |
| SSRF/Command execution via parameters | Untrusted âurl/path/modeâ extras controlling network/file operations |
| Broken access control | Exported service callable by other apps without authorization |
| Rate limiting / DoS control | Start spam, binder call spam, expensive work on main thread |
If you export a service, youâre effectively shipping an API endpoint inside your app. The difference from web is that the âclientâ is any other app on the device, not a browser. The same engineering discipline applies: authz, validation, and abuse controls.
Why Services become vulnerable
- âItâs internalâ assumptions: services are written as internal helpers, then accidentally exported or reachable through an exported receiver/activity.
- Binder complexity: developers expose rich Binder methods without thinking about caller identity and parameter trust.
- Permission misunderstandings: using weak permissions (or none) for sensitive operations; confusing âdeclared permissionâ with âenforced permissionâ.
- Privileged side effects: services often touch disk, keys, tokens, device identifiers, or network â all high-impact when controllable.
- Abuse resilience gaps: services are easy to spam; teams donât design for start/bind flooding or repeated expensive operations.
Vulnerabilities you see in real assessments
-
Exported started service accepts command-like extras
Web equivalent: unauthenticated command endpoint
The service reads extras like
action,mode,path,urland triggers privileged operations (sync, upload, export, delete cache, switch env) without verifying sender or user context. -
Exported bound service exposes privileged Binder methods
Web equivalent: internal API exposed publicly
The Binder interface offers functions like
getToken(),exportData(),setConfig(),runJob()that can be called by other apps if binding is allowed. -
Data leakage via Binder return values
Web equivalent: sensitive API response without authz
Services return identifiers, account state, tokens, PII, or file contents to untrusted callers, sometimes unintentionally (e.g., âdebugâ methods left in production).
-
Intent redirection / confused deputy through exported service
Web equivalent: server-side request made on behalf of attacker
A service performs privileged actions (network fetch, file open, content access) based on attacker-controlled parameters, turning the app into a deputy that can access things the attacker app cannot.
-
DoS via start/bind flooding or heavy work on main thread
Web equivalent: no rate limiting / expensive request handling
Repeated
startServicecalls or binder invocations cause battery drain, ANRs, or persistent foreground notifications. -
Weak permission gates (normal/dangerous instead of signature)
Web equivalent: âAPI keyâ any client can obtain
Service is protected by a permission that third-party apps can request, which is insufficient when the service exposes sensitive capabilities.
How attackers exploit Services
Service exploitation is usually about reaching a privileged execution path with attacker-controlled inputs. The most practical routes are: (a) directly starting an exported service with crafted extras, (b) binding to an exported service and calling binder methods, or (c) using an exported receiver/activity to trigger the service indirectly.
- Discovery: enumerate services, exported flags, intent-filters, permissions, and binder presence.
- Invocation: use
adb am startservice/am start-foreground-serviceto push crafted commands. - IPC abuse: if bindable, attempt to bind from another app or instrument via Frida to observe binder calls and parameters.
- Side effects: monitor logs, file changes, network requests, job scheduling, notifications, or config toggles.
- Amplification: spam start/bind to probe abuse limits and stability (DoS risk and reliability issues).
Practical testing workflow (step-by-step with commands)
1) Enumerate services and exported surface
adb shell dumpsys package com.yourapp | sed -n '/Services:/,/Receivers:/p'
Static manifest extraction:
adb shell pm path com.yourapp
adb pull /data/app/<...>/base.apk
apktool d -f base.apk -o out
grep -R "<service" -n out/AndroidManifest.xml
For each service, capture:
android:exportedandroid:permissionand whether itâs signature-level- intent-filters (rare for services, but high-signal when present)
- foreground service type / notifications behavior (abuse angle)
2) Try starting services directly
Start a service explicitly:
adb shell am startservice -n com.yourapp/.service.SyncService
Start with extras (common command pattern):
adb shell am startservice -n com.yourapp/.service.CommandService --es action "sync"
adb shell am startservice -n com.yourapp/.service.CommandService --es action "export" --es format "csv"
adb shell am startservice -n com.yourapp/.service.CommandService --es action "set_env" --es env "staging"
For Android 8+ foreground services (if applicable):
adb shell am start-foreground-service -n com.yourapp/.service.UploadForegroundService --es mode "bulk"
3) Observe what work is triggered
adb logcat | grep -i "yourapp\|Service\|onStartCommand\|Foreground"
Check running services and jobs:
adb shell dumpsys activity services | grep -i yourapp
adb shell dumpsys jobscheduler | grep -i yourapp
4) Identify binder-exposed services
If a service implements onBind() and returns a Binder/AIDL stub, it may be callable cross-app if exported.
You can confirm binder activity by instrumenting binding calls and binder transactions in a lab environment.
frida -U -f com.yourapp -l observe_service_intents.js --no-pause
// observe_service_intents.js (public-safe: logs service starts and key intent fields)
Java.perform(function () {
var Ctx = Java.use("android.content.ContextWrapper");
Ctx.startService.overload("android.content.Intent").implementation = function (i) {
try {
var c = i.getComponent();
var a = i.getAction();
console.log("[startService] action=" + a + " component=" + (c ? c.flattenToShortString() : "null"));
} catch (e) {}
return this.startService(i);
};
Ctx.startForegroundService.overload("android.content.Intent").implementation = function (i) {
try {
var c = i.getComponent();
var a = i.getAction();
console.log("[startForegroundService] action=" + a + " component=" + (c ? c.flattenToShortString() : "null"));
} catch (e) {}
return this.startForegroundService(i);
};
});
5) Validate permission enforcement
If a service declares android:permission, starting it without that permission should fail or no-op.
Confirm via logs and side effects.
adb shell am startservice -n com.yourapp/.service.PrivService --es action "test"
6) Abuse testing (controlled)
If the service triggers heavy work, test resilience by repeated starts. Youâre not trying to âcrash itâ casually; youâre validating whether it has basic abuse controls and whether it can be induced into battery/CPU drain states.
for i in $(seq 1 30); do
adb shell am startservice -n com.yourapp/.service.CommandService --es action "sync"
done
Secure code (vulnerable â fixed) for each issue
1) Exported started service accepts command-like extras
Vulnerable:
<service
android:name=".service.CommandService"
android:exported="true" />
// Kotlin
class CommandService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val action = intent?.getStringExtra("action") ?: return START_NOT_STICKY
when (action) {
"set_env" -> Config.setEnv(this, intent.getStringExtra("env") ?: "prod")
"export" -> Exporter.exportAll(this, intent.getStringExtra("format") ?: "csv")
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent?) = null
}
Fixed (reduce exposure + require strong permission + validate inputs):
<permission
android:name="com.yourapp.permission.INTERNAL_SERVICE"
android:protectionLevel="signature" />
<service
android:name=".service.CommandService"
android:exported="true"
android:permission="com.yourapp.permission.INTERNAL_SERVICE" />
// Kotlin
class CommandService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val action = intent?.getStringExtra("action") ?: return START_NOT_STICKY
when (action) {
"set_env" -> {
val env = intent.getStringExtra("env") ?: return START_NOT_STICKY
if (env !in setOf("prod", "staging")) return START_NOT_STICKY
Config.setEnv(this, env)
}
"export" -> {
val format = intent.getStringExtra("format") ?: return START_NOT_STICKY
if (format !in setOf("csv", "json")) return START_NOT_STICKY
Exporter.exportAll(this, format)
}
}
return START_NOT_STICKY
}
override fun onBind(intent: Intent?) = null
}
2) Exported bound service exposes privileged Binder methods
Vulnerable:
<service
android:name=".service.AccountBinderService"
android:exported="true" />
// Kotlin
class AccountBinderService : Service() {
private val binder = object : IAccountApi.Stub() {
override fun getToken(): String = SessionManager.getToken(this@AccountBinderService)
}
override fun onBind(intent: Intent) = binder
}
Fixed (enforce caller identity at IPC boundary):
<service
android:name=".service.AccountBinderService"
android:exported="true"
android:permission="com.yourapp.permission.INTERNAL_SERVICE" />
// Kotlin
class AccountBinderService : Service() {
private fun enforceCaller() {
val uid = android.os.Binder.getCallingUid()
val pkgs = packageManager.getPackagesForUid(uid) ?: emptyArray()
if (!pkgs.contains(packageName)) throw SecurityException("Untrusted caller")
}
private val binder = object : IAccountApi.Stub() {
override fun getToken(): String {
enforceCaller()
return SessionManager.getToken(this@AccountBinderService)
}
}
override fun onBind(intent: Intent) = binder
}
3) Data leakage via binder return values / debug methods
Vulnerable:
// Kotlin
override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array<out String>) {
writer.println("token=" + SessionManager.getToken(this))
}
Fixed (avoid emitting secrets; gate debug surfaces):
// Kotlin
override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array<out String>) {
writer.println("loggedIn=" + SessionManager.isLoggedIn())
}
4) Confused deputy via untrusted parameters (network/file)
Vulnerable:
// Kotlin
val url = intent.getStringExtra("url") ?: return
Downloader.fetch(url) // service becomes a deputy for attacker-chosen URLs
Fixed (allowlist domains/paths and bind to feature intent):
// Kotlin
val url = intent.getStringExtra("url") ?: return
val u = android.net.Uri.parse(url)
if (u.scheme != "https") return
if (u.host !in setOf("api.yourapp.com", "cdn.yourapp.com")) return
Downloader.fetch(url)
5) DoS via start spam / heavy work on main thread
Vulnerable:
// Kotlin
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
ExpensiveSync.run() // blocks, repeats, easy to spam
return START_STICKY
}
Fixed (idempotence + move work off main thread + refuse duplicates):
// Kotlin
@Volatile private var running = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (running) return START_NOT_STICKY
running = true
Thread {
try { ExpensiveSync.run() }
finally { running = false; stopSelf(startId) }
}.start()
return START_NOT_STICKY
}
6) Weak permission gate
Vulnerable:
<service
android:name=".service.PrivService"
android:exported="true"
android:permission="android.permission.INTERNET" />
Fixed (use signature permission or not exported):
<service
android:name=".service.PrivService"
android:exported="false" />
<service
android:name=".service.PrivService"
android:exported="true"
android:permission="com.yourapp.permission.INTERNAL_SERVICE" />
Interview questions & answers (Easy â Medium â Hard â Senior)
Easy
-
Whatâs the difference between a started service and a bound service?
Started services are command-style: you send an intent and the service runs. Bound services expose a Binder interface for two-way IPC. Bound services are closer to an internal API surface.
-
What does it mean if a service is exported?
Other apps can start or bind to it (depending on its implementation and intent-filters). If exported, treat inputs as untrusted and enforce permissions/caller identity.
-
How would you test an exported service quickly?
Enumerate via manifest/dumpsys, then call it with
adb shell am startservice -n package/.Serviceand vary extras to see if it triggers privileged behavior or leaks data.
Medium
-
Whatâs a common service vulnerability pattern in real apps?
A command-driven started service that performs privileged actions based on extras without verifying caller or requiring a strong permission.
-
How do you think about a bound service from a web security mindset?
Like an internal API endpoint. Every binder method needs authz, input validation, and abuse controls, because the caller can be another app process.
-
Whatâs the safest default when a service shouldnât be externally used?
Donât export it. If the app needs internal coordination, use explicit intents within the app, WorkManager/JobScheduler, or signature-protected IPC.
Hard
-
Is declaring
android:permissionenough for a bound service?Itâs necessary but not always sufficient. You still want a defense-in-depth check at the binder boundary: validate calling UID/package, and ensure each method enforces authorization relevant to the data/action. Permissions can be misdeclared or too weak; binder checks are explicit.
-
Whatâs a âconfused deputyâ risk in services?
The service has privileges (network access to app-only endpoints, file access, content URIs). If it performs actions based on attacker-controlled parameters, it can be used to perform operations the attacker app couldnât do directly.
-
How do you assess DoS risk on a service?
I check whether starts/binds are idempotent, whether work is off the main thread, whether the service can be spammed into repeated heavy work, and whether foreground notifications can be forced persistently. Then I validate with controlled repeated invocations and observe stability/battery behavior.
Senior
-
How do you systematically audit services in a large Android app?
Inventory all services with export status, permission gates, and whether theyâre started/bound. Then classify by side effect: token/session handling, file export, network fetch, account data access, configuration toggles, debug surfaces. I prioritize exported + binder + privileged side effects. For each, I test direct invocation via adb, validate caller identity enforcement, and look for parameter-driven deputy behavior.
-
What design guidance do you give teams to avoid service IPC bugs?
Keep services non-exported by default, use signature permissions for app-internal IPC, keep binder interfaces narrow and method-level authz explicit, and avoid passing secrets via intents/binders. Also build idempotence and abuse resistance: refuse duplicates and make expensive operations work-queued.
-
If a service must be exported (partner integrations), what do you require?
A clearly defined contract, a strong permission model (signature where possible, otherwise a vetted integration mechanism), explicit caller identity checks, strict input validation/allowlisting, and no sensitive data returned unless the caller is authenticated and authorized. I also require an abuse strategy (rate limiting/idempotence) because services are trivial to spam.