Ssrf Server Side in Adonisjs with Basic Auth
Ssrf Server Side in Adonisjs with Basic Auth — how this specific combination creates or exposes the vulnerability
Server Side Request Forgery (SSRF) in an AdonisJS application that uses HTTP Basic Authentication creates a scenario where an attacker can force the server to make authenticated requests to internal or external endpoints. Because AdonisJS typically handles authentication at the route or middleware layer, developers may assume that routes guarded by Basic Auth are safe from SSRF. This is not always the case when the application performs outbound HTTP calls using user-controlled data without proper validation or network segregation.
Basic Auth credentials are often stored in environment variables or configuration files and used by an HTTP client (such as axios or AdonisJS’s native HttpClient) to access protected resources. If an attacker can influence the target URL of these outbound requests—through query parameters, request body fields, or headers—they can direct the server to make authenticated requests to internal services (e.g., http://127.0.0.1:3306, metadata services like 169.254.169.254, or internal APIs). Because the request includes valid Basic Auth headers, the backend service may trust and process the request, leading to data exposure, internal network scanning, or SSRF-based pivoting.
The risk is amplified when the application uses Basic Auth for internal microservices or administrative endpoints and does not enforce strict network-level controls. For example, an endpoint like /api/proxy that accepts a URL parameter to fetch external data and adds Basic Auth headers to the outbound call can be abused to scan internal infrastructure. An SSRF scan via middleBrick against such an endpoint would flag the unauthenticated attack surface and highlight that user input directly influences outbound HTTP destinations, even when the incoming request is protected by Basic Auth.
In AdonisJS, common patterns that lead to SSRF include using HttpClient.get() with user-supplied URLs, constructing webhook URLs from request data, or dynamically setting proxy or API endpoints based on input. Even when routes are protected by Basic Auth middleware, the server-side request chain may still execute with the same credentials, bypassing intended access boundaries. This means that authentication protects the entry point but does not mitigate the impact of a maliciously crafted outbound request.
Real-world examples include CVE-2021-23352-like patterns where internal metadata is reachable and CVE-2022-22965-style scenarios where SSRF leads to cloud instance metadata exposure. Because Basic Auth transmits credentials in an encoded (not encrypted) form unless used over TLS, additional risks arise if credentials are reused across internal and external services. middleBrick’s LLM/AI Security and Data Exposure checks help identify whether outbound calls leak credentials or sensitive data, while its Input Validation and Property Authorization checks highlight insufficient validation of user-supplied URLs.
Basic Auth-Specific Remediation in Adonisjs — concrete code fixes
To mitigate SSRF in AdonisJS when using Basic Auth, ensure that outbound HTTP requests never rely on user-controlled URLs and that network boundaries are enforced. Below are concrete remediation steps with code examples.
1. Avoid dynamic URLs in HTTP clients
Do not pass user input directly into HttpClient.get() or axios. Instead, use a strict allowlist of destinations.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { HttpContext } from '@ioc:Adonis/Core/HttpContext'
import axios from 'axios'
export default class ProxyController {
public async fetchExternal(ctx: HttpContextContract) {
const { target } = ctx.request.qs()
// ❌ Dangerous: user-controlled URL
// const response = await axios.get(target, { auth: { username, password } })
// ✅ Safe: allowlist of allowed hosts
const allowedHosts = ['https://api.example.com', 'https://data.service.com']
const selected = allowedHosts.includes(target) ? target : allowedHosts[0]
const response = await axios.get(selected, {
auth: {
username: process.env.BASIC_USER,
password: process.env.BASIC_PASS,
},
})
return response.data
}
}
2. Use environment-bound credentials and disable proxying
Do not forward authentication headers to user-provided endpoints. Instead, use fixed credentials scoped to a specific service and avoid proxying user requests entirely.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import axios from 'axios'
export default class DataController {
public async getInventory(ctx: HttpContextContract) {
// ✅ Fixed destination with environment-bound Basic Auth
const response = await axios.get('https://internal.service.local/inventory', {
auth: {
username: process.env.INVENTORY_USER,
password: process.env.INVENTORY_PASS,
},
timeout: 5000,
})
return response.data
}
}
3. Validate and sanitize hosts using a URL parser
If you must accept a URL, parse it and enforce host and scheme restrictions.
import { URL } from 'url'
export function validateOutboundUrl(input: string): string | null {
let parsed: URL
try {
parsed = new URL(input)
} catch {
return null
}
if (!['https:'].includes(parsed.protocol)) {
return null
}
const allowedHosts = new Set(['api.example.com', 'data.example.com'])
if (!allowedHosts.has(parsed.hostname)) {
return null
}
return parsed.toString()
}
4. Apply network-level controls and disable outbound to private IPs
Use container or runtime policies to prevent connections to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16). This complements application-level fixes and reduces the impact of a potential SSRF vulnerability.
5. Use middleware to enforce authentication scope
Ensure that Basic Auth is applied only where necessary and does not implicitly authorize outbound calls.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { middleware } from '@ioc:Adonis/Core/BodyValidator'
export const basicAuth = async (ctx: HttpContextContract, next: () => Promise) => {
const auth = ctx.request.auth()
if (!auth || auth.username !== process物的 BASIC_USER || auth.password !== process.env.BASIC_PASS) {
ctx.response.unauthorized('Invalid credentials')
return
}
await next()
}