01Executive Summary
SecureSan Technologies was engaged by Corva Bank (a fictional regional retail bank) to perform a mobile application security assessment of its Android banking app. Following the OWASP Mobile Application Security Verification Standard (MASVS v2.1.0) and testing against the OWASP Mobile Application Security Testing Guide (MASTG), our consultants recovered hardcoded API credentials from the shipped APK, bypassed SSL certificate pinning at runtime using Frida, and then intercepted and tampered with the app's supposedly protected API traffic.
The combination allowed us to authenticate to a backend API as the application itself and reach account data that the mobile UI never exposed. The engagement produced 11 findings (2 Critical, 3 High, 4 Medium, 2 Low). All Critical and High findings were remediated, and the retest confirmed an 88% risk reduction.
An app that ships a secret ships it to every attacker who downloads it. Reverse engineering is not a sophisticated capability — it is a Tuesday.
02Client Background
Corva Bank (fictional) is a regional retail bank serving ~600,000 customers, with a mobile-first strategy. Its Android app provides balance checks, transfers, card controls, and mobile cheque deposit.
- Sector: Retail banking (regulated)
- Platform under test: Android app (Kotlin), min SDK 26, distributed via Google Play
- Backend: REST API over TLS, mutual reliance on client-side SSL pinning
- Compliance drivers: PCI DSS 4.0, local banking regulator mobile-security guidance
03Business Challenges
Corva's mobile team believed two controls made the app safe: certificate pinning (to prevent traffic interception) and code obfuscation (to prevent reverse engineering). Both are valuable, but both are client-side controls running on a device the attacker fully controls. The engagement's purpose was to test whether those controls actually held under a determined attacker, and what lay behind them.
04Engagement Scope
Black-box mobile assessment. SecureSan received the production APK and a set of test banking accounts, but no source code and no backend documentation — modelling an attacker who simply installs the app from the Play Store.
| In scope | Out of scope |
|---|---|
| Android APK (static & dynamic analysis) | iOS application |
| Client-side storage, secrets, pinning | Backend infrastructure / servers |
| API traffic reachable from the app | Denial-of-service testing |
| Runtime instrumentation on a rooted test device | Play Store / distribution channel |
All dynamic testing was performed on a dedicated rooted test device with synthetic accounts, isolated from production customer data.
05Testing Methodology
Testing followed the OWASP MASTG profile appropriate to a banking app (MAS-L2 plus resilience testing, MAS-R), verifying controls defined in MASVS v2.1.0. In MASVS v2, the old L1/L2 verification levels were removed; risk tiering now lives in the MASTG as testing profiles.
06Assessment Timeline
| Day | Activity |
|---|---|
| 1 | APK acquisition, unpacking (apktool), decompilation (jadx), storage review |
| 2 | Secret discovery — recovered hardcoded API key and endpoints |
| 3 | Frida instrumentation; SSL pinning bypass; traffic interception |
| 4 | API abuse validation with recovered credentials; impact assessment |
| 5 | Reporting & remediation workshop |
| +30 | Retest — 88% risk reduction confirmed |
07Environment Details
| Item | Detail |
|---|---|
| App | Kotlin, Retrofit + OkHttp networking, min SDK 26 |
| Pinning | OkHttp CertificatePinner with a hardcoded SHA-256 pin |
| Obfuscation | R8/ProGuard name obfuscation (strings not encrypted) |
| Test device | Rooted Android 13, Magisk, Frida server |
08Tools Used
- apktool — unpack the APK, read the manifest and resources
- jadx — decompile DEX to readable Java/Kotlin
- MobSF — automated static analysis and secret scanning
- Frida — runtime instrumentation and pinning bypass
- objection — Frida-powered runtime exploration
- Burp Suite — proxy for intercepting API traffic once pinning was defeated
09Attack Surface Analysis
The mobile attack surface splits into three layers, all under attacker control on the device:
| Layer | What we tested | MASVS category |
|---|---|---|
| Static (the shipped binary) | Hardcoded secrets, endpoints, logic in decompiled code | MASVS-CODE, MASVS-CRYPTO |
| Local storage | SharedPreferences, SQLite, logs, backups | MASVS-STORAGE |
| Runtime / network | Pinning, traffic, anti-tamper resilience | MASVS-NETWORK, MASVS-RESILIENCE |
10Reconnaissance Process
We unpacked the APK and decompiled it. String obfuscation was not applied — R8 renamed classes and methods but left string literals in cleartext. A targeted grep of the decompiled sources surfaced immediate red flags:
# Search decompiled sources for common secret patterns
$ grep -rEn "api[_-]?key|secret|password|Bearer|https://" ./sources | head
sources/com/corva/net/ApiConfig.java:14: static final String BASE_URL =
"https://api.corvabank.example/v3/";
sources/com/corva/net/ApiConfig.java:15: static final String API_KEY =
"cb_live_8f2a19c4d7e6b0a3...";
sources/com/corva/net/Pinning.java:9: .add("api.corvabank.example",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
The presence of a live-looking key and a hardcoded pin in the decompiled sources set the direction for the rest of the assessment: recover the key, defeat the pin, and see what the two together unlock.
11Vulnerability Discovery
Finding 1 — Hardcoded API key in the shipped binary (Critical)
A live backend API key was embedded as a string constant. Every install of the app carries it. This violates MASVS-CRYPTO and MASVS-STORAGE: secrets must never be shipped in the client binary, because the client binary is fully readable by any attacker.
Finding 2 — Bypassable SSL pinning as the only transport defence (High)
The pin was hardcoded in Pinning.java. Pinning raises the effort of interception but, running on a rooted device, can always be defeated at runtime. Because the API also trusted the shipped key, pinning was the only thing standing between an attacker and the traffic.
Finding 3 — Insecure local storage & verbose logging (Medium)
Session artefacts were written to SharedPreferences in cleartext and sensitive values appeared in application logs, and allowBackup was enabled — allowing extraction of app data via ADB backup on a compromised device (MASVS-STORAGE).
12Technical Exploitation Walkthrough
Step 1 — Confirm pinning blocks a naive proxy
With Burp configured as the device proxy and its CA installed, the app refused to connect — pinning working as designed:
okhttp3.CertificatePinner$1: Certificate pinning failure!
Peer certificate chain: ...
Pinned certificates for api.corvabank.example: [sha256/AAAA...]
Step 2 — Bypass pinning at runtime with Frida
We attached Frida and neutralised the CertificatePinner.check() method so it returns without validating:
// frida-pinning-bypass.js — neutralise OkHttp CertificatePinner
Java.perform(function () {
const CP = Java.use('okhttp3.CertificatePinner');
CP.check.overload('java.lang.String', 'java.util.List')
.implementation = function (host, peerCerts) {
console.log('[+] Pinning bypassed for ' + host);
return; // skip validation entirely
};
});
$ frida -U -f com.corva.bank -l frida-pinning-bypass.js
[+] Pinning bypassed for api.corvabank.example
Step 3 — Intercept and read the cleartext API traffic
With pinning defeated, Burp captured the full request/response flow. The app authenticated to the API using the recovered key plus the user's session token:
GET /v3/accounts/summary HTTP/1.1
Host: api.corvabank.example
X-Api-Key: cb_live_8f2a19c4d7e6b0a3...
Authorization: Bearer <user session token>
Step 4 — Abuse the recovered key against the API directly
We took the key out of the app entirely and called the API from a plain HTTP client. The key was accepted, confirming it was a real trust boundary the app leaked:
$ curl -s https://api.corvabank.example/v3/products \
-H "X-Api-Key: cb_live_8f2a19c4d7e6b0a3..."
HTTP/1.1 200 OK # key accepted outside the app
Endpoints that additionally required a user token were still protected by that token — but any endpoint gated only by the app key was now reachable by anyone, and the interception capability exposed the full request structure for further testing.
13Attack Chain
Three client-side weaknesses combined to defeat the app's transport security:
14Business Impact
- Leaked trust boundary. A live API key shipped to every device. Rotating it means shipping an app update to 600,000 devices — a slow, painful control failure.
- Interception capability. Once pinning is bypassed, an attacker can observe and tamper with all API traffic, aiding discovery of further server-side flaws.
- Regulatory & PCI exposure. Hardcoded credentials in a banking app are a direct finding against PCI DSS 4.0 and mobile-security regulatory guidance.
- Reputational risk. "Bank app ships its own API key" is a headline, not a footnote.
15MITRE ATT&CK Mapping
| Tactic | Technique | ID |
|---|---|---|
| Credential Access | Unsecured Credentials: Credentials In Files | T1552.001 |
| Defense Evasion | Impair Defenses (bypass pinning) | T1562 |
| Collection | Adversary-in-the-Middle | T1557 |
| Discovery | Software Discovery / API enumeration | T1518 |
MITRE maintains a dedicated ATT&CK for Mobile matrix; the techniques above are drawn from the enterprise and mobile matrices as applicable to on-device attacks.
16CVSS Scoring
| Finding | CVSS 3.1 vector | Score | Severity |
|---|---|---|---|
| Hardcoded live API key | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N | 8.2 | Critical |
| Bypassable pinning (sole transport control) | AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N | 6.8 | High |
| No string encryption / weak resilience | AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N | 3.3 | Low |
17Risk Matrix
| Likelihood → / Impact ↓ | Low | Medium | High |
|---|---|---|---|
| Severe | Hardcoded API key | ||
| Major | Pinning bypass → MitM | ||
| Moderate | Insecure logging | Backup allows data extraction | |
| Minor | No string encryption |
18Evidence Collected
- Decompiled source excerpts showing the hardcoded key and pin
- MobSF report flagging the embedded secrets
- Frida script and console output proving the pinning bypass
- Burp captures of previously-pinned HTTPS traffic
- curl proof that the key is accepted outside the app
Indicators of Compromise
# Detectable server-side signals of key abuse:
- X-Api-Key used from IPs/user-agents inconsistent with the mobile app
- API key present without an accompanying valid user session token
- Requests with app key but non-mobile TLS fingerprints (JA3)
19Root Cause Analysis
- Client-side secrets are not secret. Anything compiled into the app is readable. The API key should never have existed in the binary.
- Client-side controls are advisory, not authoritative. Pinning and obfuscation raise attacker cost; they do not enforce security on a device the attacker owns.
- Server-side trust must not depend on a shared client secret. The backend trusted a value that every user physically possesses.
20Remediation Recommendations
Immediate
- Revoke the leaked API key. Remove all secrets from the binary.
- Move any per-app authentication to a mechanism that does not embed a shared static secret — e.g. per-device attestation (Play Integrity API) plus short-lived, server-issued tokens bound to an authenticated user.
// Do NOT ship secrets. Fetch a short-lived, device-bound token after
// authenticating the USER, gated by Play Integrity attestation.
val token = api.exchangeIntegrityToken(
integrityVerdict = playIntegrity.requestToken(),
userCredential = userLogin
) // server issues a scoped, expiring token — nothing static in the APK
Short term
- Treat pinning as defence-in-depth, never as the sole transport control. Assume it will be bypassed and ensure the server enforces authorization independently.
- Add runtime integrity checks (root/Frida detection) appropriate to MAS-R — understanding these raise cost but are not absolute.
- Encrypt sensitive strings; disable
allowBackup; strip verbose logging from release builds.
Strategic
- Integrate MobSF and secret scanning into the mobile CI pipeline so a re-introduced secret fails the build.
- Adopt the OWASP MASVS as a standing requirement baseline for every release.
21Security Improvements After Remediation
At retest:
- The API key was revoked and removed; the app now obtains a short-lived, user- and device-bound token from the server.
- The backend no longer accepts a static app key as an authentication factor.
- Sensitive strings encrypted; backups disabled; release logging stripped.
- Root/Frida detection added as defence-in-depth.
$ curl -s https://api.corvabank.example/v3/products \
-H "X-Api-Key: cb_live_8f2a19c4d7e6b0a3..."
HTTP/1.1 401 Unauthorized # revoked key rejected; static-key auth removed
22Lessons Learned
- Never ship a secret. If the client needs to prove something, prove the user and the device, and issue short-lived server-side tokens — do not embed a static credential.
- Design assuming a rooted device. Pinning, obfuscation, and anti-tamper are cost multipliers, not guarantees.
- Reverse engineering is routine. An hour with jadx and Frida is all it took. Threat-model for it.
23Key Metrics
24Conclusion
Corva Bank's app relied on two client-side controls — pinning and obfuscation — to protect a secret that should never have been in the binary. Both controls were bypassed within an hour on a device the attacker fully controlled. The durable fix removed the static secret entirely and moved trust to server-issued, device-attested, short-lived tokens.
Why This Matters
Every mobile app you publish is handed, in full, to your adversary. They can decompile it, instrument it, and strip out any control that runs on the device. The only security that holds is the security your server enforces — and any secret you compile into the client is a secret you have already given away.
A five-day mobile assessment found a live banking API key before an attacker did, and turned "we ship our own credentials" into "we ship nothing an attacker can use." That is the return on a proactive test: the finding cost days; the breach it prevented would have cost the brand.