đŸ›Ąïž Application Security CheatSheet

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

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)

WebAndroid
Internal API endpointBound service (Binder interface)
Command endpoint / queue consumerStarted service (intent-driven command handler)
Authentication middlewarePermission gate + calling UID validation + session binding
SSRF/Command execution via parametersUntrusted “url/path/mode” extras controlling network/file operations
Broken access controlExported service callable by other apps without authorization
Rate limiting / DoS controlStart 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

  1. “It’s internal” assumptions: services are written as internal helpers, then accidentally exported or reachable through an exported receiver/activity.
  2. Binder complexity: developers expose rich Binder methods without thinking about caller identity and parameter trust.
  3. Permission misunderstandings: using weak permissions (or none) for sensitive operations; confusing “declared permission” with “enforced permission”.
  4. Privileged side effects: services often touch disk, keys, tokens, device identifiers, or network — all high-impact when controllable.
  5. 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

  1. Exported started service accepts command-like extras
    Web equivalent: unauthenticated command endpoint

    The service reads extras like action, mode, path, url and triggers privileged operations (sync, upload, export, delete cache, switch env) without verifying sender or user context.

  2. 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.

  3. 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).

  4. 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.

  5. DoS via start/bind flooding or heavy work on main thread
    Web equivalent: no rate limiting / expensive request handling

    Repeated startService calls or binder invocations cause battery drain, ANRs, or persistent foreground notifications.

  6. 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.

  1. Discovery: enumerate services, exported flags, intent-filters, permissions, and binder presence.
  2. Invocation: use adb am startservice / am start-foreground-service to push crafted commands.
  3. IPC abuse: if bindable, attempt to bind from another app or instrument via Frida to observe binder calls and parameters.
  4. Side effects: monitor logs, file changes, network requests, job scheduling, notifications, or config toggles.
  5. 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:

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

  1. 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.

  2. 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.

  3. How would you test an exported service quickly?

    Enumerate via manifest/dumpsys, then call it with adb shell am startservice -n package/.Service and vary extras to see if it triggers privileged behavior or leaks data.

Medium

  1. 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.

  2. 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.

  3. 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

  1. Is declaring android:permission enough 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.

  2. 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.

  3. 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

  1. 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.

  2. 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.

  3. 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.