Testing Mobile Apps — A Working Methodology
Most mobile "findings" are noise — a hardcoded string, a debug flag, a screenshot of a decompiled method. The bugs that pay are in the API the app talks to and in the trust the client assumes but the server fails to enforce. Here's the order we work an engagement, Android and iOS.
1. Static first — it's free and it maps the surface
Pull the package (apk/ipa) and unpack it. You're not looking for a magic secret; you're building a map:
- Endpoints and hosts. Every base URL, API path, and third-party host the app talks to. This is your target list — the API is the real attack surface.
- Secrets that actually matter. Not the analytics key everyone reports — signing keys, cloud credentials, and tokens with server-side power. Grep the binary, resources, and native libs.
- Client-side trust. Look for logic the app thinks is protecting something: client-side auth checks, "premium" gates, price/entitlement decisions, and validation that a rebuilt or intercepted client can simply skip.
- Platform hygiene. Exported components and deep links (Android), URL scheme handlers and universal links (iOS), insecure storage,
allowBackup, and debuggable flags. These are real but usually lower-severity — note them, don't stop here.
2. Network — where the money is
Proxy the traffic. This is 80% of the value:
- Defeat pinning to see the API. Certificate pinning stops a lazy proxy; Frida-based trust-all or objection unpins most apps in minutes. Once you can read the traffic, the mobile app becomes "just an API client," and every web technique applies.
- Test the API as the untrusted client it is. BOLA/IDOR on object IDs, broken function-level authorization on admin-ish endpoints, mass assignment, and the same business-logic and money-movement classes as any web target. The app's UI hides these; the API doesn't enforce them.
- Token and session handling. How are tokens issued, refreshed, scoped, and revoked? Is a token bound to a device, or portable? Does logout actually invalidate server-side?
The recurring root cause: the server trusts the client to have already checked something. It hasn't, and you don't have to either.
3. Dynamic — instrument the running app
With Frida/objection on a rooted/jailbroken device or emulator:
- Bypass the client-side gates you found statically — root/jailbreak detection, pinning, "is premium" booleans — and confirm the server doesn't re-check. A premium feature that unlocks by flipping a client boolean is a real finding only if the server serves the premium data anyway; verify at the API.
- Inspect storage at rest. Keystore/Keychain usage,
SharedPreferences/NSUserDefaults, SQLite caches, and logs. Sensitive data written in cleartext, or protected only by a client check, is a finding. - Watch logcat / device logs during sensitive flows for tokens, PII, or full requests leaking to a channel other apps can read.
The severity reality
Rank findings by server-side impact:
- High: API authorization flaws (BOLA/BFLA on other users' data or actions), account takeover, money-movement logic, secrets granting server-side access.
- Medium: sensitive data in insecure storage or logs, missing transport protections that expose the above.
- Low / informational: missing pinning by itself, root-detection bypass by itself, an
allowBackupflag with nothing sensitive behind it. Report them, but don't inflate them — a control's absence isn't impact until you show what it exposes.
Discipline
Use throwaway accounts, test cross-user access with a second account you own, and never pull another user's real data to "prove" IDOR — a request to your own second account's object, denied or allowed, proves it cleanly. The app is the doorway; the API is the room. Spend your time in the room.