Api Version Exploitation in Adonisjs
How Api Version Exploitation Manifests in Adonisjs
API version exploitation in AdonisJS occurs when an application exposes multiple API versions (e.g., /api/v1, /api/v2) but fails to properly restrict access, deprecate old endpoints, or enforce consistent security controls across versions. Attackers probe for unversioned endpoints or manipulate version identifiers (via URL path, query parameters, or headers like Accept-Version) to access older, often less secure implementations. This is a specific manifestation of BOLA/IDOR (Broken Object Level Authorization / Insecure Direct Object References) where version selection itself becomes an unauthorized access vector.
AdonisJS applications commonly implement versioning using route groups or the @version decorator. Vulnerabilities arise when:
- Unversioned endpoints exist: A route like
Route.get('/users', ...)might implicitly serve the oldest version (v1) while newer versions (/api/v2/users) have stricter authentication. An attacker accessing the unversioned path bypasses newer security controls. - Deprecated versions remain active: After releasing v2, v1 routes are left accessible without deprecation headers (
Deprecation: true) or access restrictions, exposing known vulnerabilities fixed in later versions (e.g., CVE-2023-1234 in an old user endpoint). - Version validation is absent: If versioning is header-based (e.g.,
Accept: application/vnd.api.v1+json), AdonisJS might not validate the header against an allowlist, allowing attackers to request any version, including internal or debug versions.
Consider this vulnerable AdonisJS route setup (TypeScript):
// routes/api.ts
import Route from '@ioc:Adonis/Core/Route'
// Vulnerable: unversioned endpoint with weak auth
Route.get('users', async ({ auth, response }) => {
// v1 allows any authenticated user to list all users
if (!auth.user) return response.unauthorized()
return await User.all() // Exposes all user PII
})
// v2 endpoint with proper scope checks
Route.group(() => {
Route.get('users', 'UsersController.index').as('v2.users')
}).prefix('api/v2')Here, /users (v1) is accessible without the stricter scope checks applied in v2. An attacker who knows the v1 path can enumerate all users, violating OWASP API Top 10:2023 – BOLA. If v1 uses a weak password hashing algorithm (e.g., bcrypt with low rounds), this also violates PCI-DSS requirement 8.2.
Adonisjs-Specific Detection
Detecting API version exploitation requires testing how the application responds to different version indicators. middleBrick's BOLA/IDOR and Authentication checks automatically probe for version-related inconsistencies during its 5–15 second black-box scan. It sends requests with varied version parameters (e.g., /api/v1 vs. /api/v2, Accept-Version headers) and compares responses for:
- Unauthorized access: Whether older versions allow actions blocked in newer versions (e.g., v1 returns 200 with full data, v2 returns 403).
- Deprecated endpoint exposure: Presence of
Deprecationheaders or HTTP 410 status for old versions. - Version header bypass: If header-based versioning is used, whether arbitrary version values are accepted.
For an AdonisJS app, scan the base URL (e.g., https://api.example.com) with middleBrick. The CLI command is:
middlebrick scan https://api.example.comThe resulting report will flag version exploitation under the BOLA/IDOR or Authentication categories, with a severity based on the sensitivity of exposed data. For example, if /api/v1/users returns email addresses while /api/v2/users requires admin scope, middleBrick assigns a high severity (letter grade D or F) and provides remediation guidance.
You can also analyze your OpenAPI/Swagger spec (if available) with middleBrick's spec parser. It cross-references the declared versions with runtime behavior. If your spec lists only v2 but runtime responds to v1 paths, this discrepancy is flagged as a Inventory Management issue (untracked endpoints).
Manual verification steps (complementary to scanning):
| Test | curl Example | Expected Safe Response |
|---|---|---|
| Request unversioned path | curl -i https://api.example.com/users | 404 Not Found or 301 to current version |
| Request deprecated version | curl -i -H "Accept: application/vnd.api.v1+json" https://api.example.com/api/users | 410 Gone with Deprecation: true |
| Request non-existent version | curl -i https://api.example.com/api/v99/users | 400 Bad Request (unsupported version) |
Adonisjs-Specific Remediation
Remediation in AdonisJS involves strictly controlling version exposure, deprecating old endpoints, and ensuring consistent security policies across all versions. Use AdonisJS's built-in routing and middleware features to enforce versioning at the framework level.
1. Enforce versioned routes only: Avoid unversioned endpoints. All API routes should reside under a versioned prefix (e.g., /api/v1). Use route groups to apply version-specific middleware and controllers.
// routes/api.ts
import Route from '@ioc:Adonis/Core/Route'
// All routes under /api/v1
Route.group(() => {
Route.get('users', 'UsersController.index').as('v1.users')
// ... other v1 routes
}).prefix('api/v1')
// All routes under /api/v2 (with stricter auth)
Route.group(() => {
Route.get('users', 'UsersV2Controller.index').as('v2.users')
}).prefix('api/v2').middleware('auth:api')2. Deprecate old versions properly: When retiring a version, return 410 Gone and set Deprecation headers. Use AdonisJS's response object:
// app/Controllers/Http/DeprecatedController.ts
export default class DeprecatedController {
public async index({ response }: HttpContextContract) {
response.header('Deprecation', 'true')
response.header('Link', '; rel="deprecation"; type="text/html"')
return response.status(410).send({
error: 'API version v1 is deprecated. Please migrate to v2.'
})
}
} Then route /api/v1/* to this controller.
3. Validate version headers: If using header-based versioning (e.g., Accept: application/vnd.api.v1+json), create middleware to validate against an allowlist:
// app/Middleware/ValidateApiVersion.ts
export default class ValidateApiVersion {
public async handle({ request, response }: HttpContextContract, next: () => Promise) {
const accept = request.header('accept') || ''
const supported = ['application/vnd.api.v2+json']
if (!supported.some(v => accept.includes(v))) {
return response.status(400).json({
error: 'Unsupported API version. Use application/vnd.api.v2+json.'
})
}
await next()
}
} Apply this middleware to all API routes: Route.group(() => {...}).middleware('validateApiVersion').
4. Consistent security policies: Ensure authentication, rate limiting, and input validation middleware are applied uniformly across all versions. Use shared middleware or base controllers to avoid version-specific gaps.
5. Monitor with middleBrick: Add middleBrick to your CI/CD pipeline (GitHub Action) to scan staging APIs before deploy. The Pro plan's continuous monitoring will alert you if old versions accidentally become accessible after a deployment. Example GitHub Action config:
- name: Scan API with middleBrick
uses: middlebrick/scan-action@v1
with:
url: ${{ env.STAGING_API_URL }}
fail_below_score: 80 # Fail build if score drops below BThis ensures versioning regressions are caught early.
FAQ
Q: How does middleBrick specifically detect API version exploitation in AdonisJS?
A: middleBrick probes multiple version indicators (URL paths like /api/v1, headers like Accept-Version) and compares responses across versions. It flags inconsistencies where older versions return data or status codes that bypass security controls (e.g., v1 returns 200 with full user list while v2 returns 403). These findings appear under the BOLA/IDOR or Authentication categories with severity based on data sensitivity.
Q: What's a common AdonisJS mistake that leads to version exploitation?
A: Leaving unversioned endpoints (e.g., /users) active alongside versioned ones (/api/v2/users). AdonisJS route definitions without a prefix default to the root path, often serving the oldest, least secure implementation. Attackers discover these via brute-force or documentation leaks. Always prefix all API routes under a version (e.g., Route.group().prefix('api/v1')) and return 404 for any unversioned API path.