Skip to main content

All weeks · Worksheet · Overview · Attack notes

Week 10 · Lecture slides

Week 10

Contents21 sections

API Security

Software Security · Nutthakorn Chalaemwongwan


Today

  • REST/GraphQL attack surface
  • OWASP API Security Top 10
  • BOLA & mass assignment
  • Rate limiting & resource consumption
  • 🎮 Game: crAPI Raid

Recap — Web half

  • Injection, XSS, auth/IDOR
  • APIs concentrate all of these — and add their own

The web half, at API scale

The web-half bugs reappearing at API scale: Injection/SQLi (W4), XSS (W5) and Auth/IDOR (W6) all return; IDOR becomes BOLA (API1); and APIs add their own — mass assignment (API3) and no rate limit (API4). The point: there is no browser to hide behind, so the API endpoint is the whole attack surface, every field attacker-chosen.


Why APIs are different

  • Machine-to-machine, no browser to "protect" them
  • Object IDs everywhere → ripe for IDOR/BOLA
  • Clients can send any field → mass assignment
  • Maps to the OWASP API Security Top 10:2023 (still current — no 2025 revision)

Side-by-side of why an API is a harder target than a browser-fronted web app. Left, the classic web app: a user → the browser (a free safety layer: same-origin, SameSite cookies, CSP, CSRF tokens) → the server, which is partly shielded because the browser enforces rules before a request leaves. Right, the API: an attacker-controlled client (curl, Postman, a script, a modified mobile app) talks straight to a raw JSON endpoint — no browser, no safety net, every byte and field attacker-chosen, so the endpoint itself is the whole attack surface. Two consequences: REST exposes object IDs, so incrementing one gives IDOR/BOLA; and the client can send any field, so binding the whole body gives mass assignment. The browser was doing security work you never noticed — remove it and every input must be treated as attacker-chosen.


BOLA (API1) — the #1 API risk

GET /api/users/2/orders   → bob's orders — while authenticated as alice
  • Broken Object Level Authorization = IDOR, at API scale
  • The catch: the vulnerable endpoint doesn't check ownership at all — it hands back whatever <id>'s orders you ask for, full stop. There's no spoofable-but-present check to defeat; there's no check. (The X-User-Id header exists as this lab's stand-in for "auth," but on this route the code never even reads it.)

BOLA shown as insecure versus secure. On the insecure API (:8080), alice (authenticated) sends GET /api/users/2/orders — asking for id 2, not hers — and because the code never reads who is calling, there is no check to spoof and none runs, so the server returns bob's orders with 200 OK. On the secure API (:8081) one ownership check produces a three-rung ladder: no identity header → 401 Unauthorized; alice (X-User-Id 1) requesting carol's id 3 → 403 Forbidden (she's known, but the object isn't hers); alice requesting her own id 1 → 200 OK. The whole fix is one question asked every request — does the caller own this object? — not escaping or a WAF rule, since the JSON was always well formed.


BOLA — change the id, watch it leak

Pick a login, request another user's id, flip the server. The ladder is live.

Whose orders come back? Ownership check on vs off (Week 10) — open full size

Mass assignment (API3)

POST /api/users  { "username":"x", "password":"y", "is_admin":true, "balance":9999 }
  • Client sets fields the server blindly binds (user.update(body) — no allow-list)
  • Privilege/balance escalation, right at account creation
Which fields actually get stored? (Week 10) — open full size

More of the Top 10

  • API2 — broken authentication (this week's X-User-Id header trick is a live example)
  • API4 — unrestricted resource consumption: no rate limit on /api/login → today's third graded bug (5 failed attempts, then 429)
  • API6 — unrestricted access to sensitive business flows
  • Under 2023's taxonomy, "excessive data exposure" isn't its own number — it's folded into API3 BOPLA alongside mass assignment (you may see it listed separately elsewhere, incl. crAPI's own docs — that's the older 2019 split)

The whole map — API Top 10:2023

The ten OWASP API Security Top 10:2023 categories as a single map, with API1 (BOLA), API3 (BOPLA / mass assignment) and API4 (unrestricted resource consumption) highlighted as this week's three graded bugs and the other seven shown as the rest of the map. Footnote: API3:2023 BOPLA folds in the old 2019 "excessive data exposure". The three we drill are authorization and design bugs, not input-validation bugs.


API4 — watch the lockout window

The fixed server allows 5 attempts per 60s per IP, checked before the password. Hammer it; advance the clock; find the lockout.

How many tries before the door locks? The 5-per-60s window, live (Week 10) — open full size

Defenses

  • Object-level authorization on every request (check ownership)
  • Allow-list request schemas — bind only intended fields
  • Rate limiting / quotas
  • Return only needed fields (DTOs)
  • Schema validation (OpenAPI / GraphQL types)

Each defense, and the attack it kills

Each Week 10 API defense mapped to the attack it stops: object-level authorization (an ownership check) kills BOLA (API1), allow-listing the request schema kills mass assignment (API3), rate limiting kills unrestricted resource consumption (API4), plus schema validation and response DTOs as defense-in-depth. These are authorization and design controls, not escaping or a WAF, because the JSON was always well formed — the bug was in what the server allowed, not how it was encoded.


Complementary: black-box recon cheat-sheet

When you only have an IP/URL (Kali):

netdiscover                 # find hosts
nmap -sV target             # ports/services (80/443?)
nikto -h http://target      # web server issues
dirb  http://target         # hidden paths
wpscan --url http://target  # if WordPress
hydra ... http-post-form    # password attack

General awareness — no worksheet task uses these against today's target; useful for the CTF weeks later in the term.


Real-world: feature abused as backdoor

WordPress 404-template RCE → bind shell:

Appearance → Theme Editor → 404 Template → insert exec(...)
# trigger by visiting any non-existent page
nc -lvp 34567 -e /bin/bash   # attacker gets a shell

Legitimate admin features become RCE without strict authz + integrity checks.


That backdoor, step by step

A five-step attack chain turning a legitimate WordPress admin feature into RCE: (1) the attacker already has wp-admin access, (2) opens Appearance → Theme Editor → 404.php, (3) pastes a PHP exec() payload saved straight to disk as part of the active theme, (4) visits any non-existent page so the 404 runs 404.php and the payload executes, (5) gets an nc bind shell as the web-server user. A legitimate admin feature becomes RCE without authorization and integrity checks — separate content editing from code editing, and version, sign and verify theme files.


One API, three flaws

One REST API with three separate flaws, each in a different handler. GET /api/users/{id}/orders never compares owner to caller — API1 BOLA. POST /api/users splats the whole JSON body into the model — API3 mass assignment. POST /api/login has no counter or lockout — API4 unrestricted resource consumption. All three are authorization and design flaws, not input-validation flaws — the JSON is well formed, so no amount of escaping, encoding, or a WAF rule blocks any of them.


🥷 Today's raid — the graded target

Local API, docker compose up → :8080 (vulnerable) / :8081 (fixed):

  1. BOLA: 401 → 403 → 200 — probe, get denied, then read another user's orders as yourself
  2. Mass assignment: smuggle is_admin/balance into account creation
  3. Resource consumption: 401×5 → 429×2 on /api/login
  4. Defend: switch to the pre-written solution_api.py, cite the exact fix line for each

Bonus (optional, ~20 min): crAPI — OWASP's own intentionally-vulnerable API, real GUID-based BOLA, capture-only, no fix step, no separate grade


Deliverable

📋 Worksheet 10 — labs/week10-api-security/worksheet.md (Part 3) · kickoff: docker compose up → :8080 (insecure) / :8081 (secure)

  • Findings mapped to the API Top 10:2023 + your two flags (FLAG_BOLA, FLAG_MASSASSIGN — public demo values on a local run: practice, not personal)
  • Fixes (authz checks, schemas, limits) — cited from solution_api.py
  • Proof exploits now fail
  • + Audit the AI / EiPE / Prompt Problem (see worksheet)

Key takeaways

  • BOLA/IDOR is the dominant API bug — check ownership
  • Bind only fields you intend
  • Validate and throttle everything

Questions?

Next week: Memory-safety & exploitation

All weeks in Software Security 2 (KOSEN)