Overview¶
Two Attackers, One Broken Trust Model¶
Consider two realistic scenarios.
Scenario A: An external attacker downloads the APK from a public store or pulls it from a device they briefly had access to. No credentials, no source code. Just the binary — and on Android, the binary is enough. Within an hour of static analysis they have the backend URL, the SSL pinning fingerprint, and a clear map of the authentication logic. Within two hours, they are reading other users' data.
Scenario B: A legitimate user decides to see what they can access beyond their own account. They log in normally, intercept their own traffic, and start probing the API with their valid token. The server never checks whether the resource they are requesting belongs to them. One parameter change is enough.
Both scenarios succeed for the same reason: the application treats the client as a trust boundary. Protections are implemented client-side where the attacker has full control. The backend trusts the client to enforce rules it never validates itself.
This lab demonstrates both attack paths end to end, chains the individual findings, and explains why fixing the client-side protections without fixing the backend only solves half the problem.
The Application: What It Appears to Be vs. What It Is¶
The target is a Flutter-based Todo app backed by a Node.js REST API. It appears to follow standard mobile security practices — SSL pinning, root detection, JWT authentication.
| What the app appears to do | What it actually does |
|---|---|
| Enforce SSL pinning | Pins at the Dart layer — bypassable at the Flutter engine (BoringSSL) level |
| Detect rooted devices | Three safe_device checks — each hookable via Frida native offsets |
| Protect sensitive configuration | Hardcodes backend URL, pinning fingerprint, and admin token in libapp.so |
| Control access via JWT | Issues tokens the server never fully validates for authorization |
| Restrict admin functions | Exposes role as a writable field — any user can self-promote |
The gap between what the app appears to enforce and what it actually enforces is the entire attack surface.
How the Findings Connect¶
Path 1 — External Attacker (No Credentials)
Download APK
↓
Static analysis → backend URL, pinning fingerprint, x-admin-token, protection call chain
↓
Frida root bypass → hook safe_device functions via ARM64 offsets from blutter
↓
Binary patch libflutter.so → NOP the BoringSSL verification branch
↓
Full HTTPS visibility via Burp Suite
↓
Register account → obtain valid JWT
↓
IDOR + privilege escalation → read any user's data, self-promote to admin
Path 2 — Malicious Legitimate User (Valid Credentials)
Log in normally → obtain valid JWT
↓
Intercept own traffic → observe API structure
↓
IDOR → substitute other users' resource IDs
↓
PUT /users/{id} with "role": "admin" → privilege escalation
↓
Admin access confirmed in mobile app
Path 2 requires no reverse engineering, no bypass tooling, no binary analysis. A motivated user with a proxy and a valid account reaches the same outcome as a sophisticated external attacker. That asymmetry is the most important finding in this lab.
Scope¶
- Android only — Flutter RE methodology transfers to iOS but tooling and bypass techniques differ
- No advanced obfuscation — R8 aggressive mode, NDK-level protections, and OLLVM are out of scope
- Manual only — every attack is performed manually; no scanner output