Ssrf in Adonisjs
How SSRF Manifests in Adonisjs
Server-Side Request Forgery (SSRF) in Adonisjs applications often occurs through HTTP client usage patterns that accept user-controlled URLs. The framework's HTTP client and request handling create specific attack surfaces that developers must understand.
A common pattern in Adonisjs applications involves accepting URLs from HTTP requests and using them to make outbound requests. For example:
class FileService {
async downloadFile({ request }) {
const url = request.input('file_url');
const response = await axios.get(url); // SSRF vulnerability
return response.data;
}
}This code is vulnerable because an attacker can provide internal network URLs like http://localhost:8080 or http://internal-service:9000, allowing them to probe internal systems through your Adonisjs application.
Adonisjs's dependency injection and service container patterns can inadvertently create SSRF opportunities. Consider this pattern:
class ExternalApiService {
async callService({ request }) {
const endpoint = request.input('service_endpoint');
const config = Config.get('services.external');
const fullUrl = `${config.base_url}${endpoint}`;
// Vulnerable to SSRF through endpoint parameter
const response = await Http.get(fullUrl);
return response.body();
}
}Another Adonisjs-specific scenario involves the framework's built-in HTTP client and URL validation. Many developers use validate with url rules without realizing they only check format, not destination:
const { validate } = use('Validator');
async processUrl({ request }) {
const rules = {
target_url: 'required|url'
};
const validation = await validate(request.all(), rules);
if (validation.fails()) {
return {
success: false,
message: 'Invalid URL'
};
}
// Still vulnerable! Only validates format, not destination
const response = await Http.get(request.input('target_url'));
return response.body();
}Adonisjs applications often integrate with microservices architectures where internal service discovery relies on predictable hostnames. An SSRF vulnerability could allow an attacker to access services like http://redis:6379, http://postgres:5432, or http://vault:8200 through your application.
Adonisjs-Specific Detection
Detecting SSRF vulnerabilities in Adonisjs requires understanding both the framework's patterns and the specific attack surface. middleBrick's black-box scanning approach is particularly effective for Adonisjs applications because it tests the actual runtime behavior without needing source code access.
middleBrick scans Adonisjs applications by submitting test payloads to endpoints that might accept URLs. For SSRF detection, it uses a comprehensive test matrix:
# Using middleBrick CLI to scan an Adonisjs API
middlebrick scan https://api.yourservice.com
# Or integrate into Adonisjs CI/CD
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
ssrf-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run middleBrick Scan
run: |
npx middlebrick scan https://staging.yourservice.com
continue-on-error: true
- name: Fail on high risk
if: failure()
run: |
echo "SSRF vulnerability detected. Review middleBrick report."
exit 1middleBrick's SSRF detection for Adonisjs specifically tests for:
- Localhost and loopback address access (
localhost,127.0.0.1,::1) - Private IP ranges (10.x, 172.16-31.x, 192.168.x)
- Internal domain resolution (using a custom DNS resolver)
- Cloud metadata services (
http://169.254.169.254for AWS/Azure) - Common service ports (Redis 6379, PostgreSQL 5432, MongoDB 27017)
- Unix socket access attempts
The scanner analyzes Adonisjs-specific patterns like HTTP client usage in controllers, middleware that processes external URLs, and service classes that make outbound requests. It maps findings to OWASP API Top 10 SSRF category and provides severity scores based on the potential impact.
For development teams, middleBrick's dashboard shows SSRF risk trends across your Adonisjs APIs, helping prioritize remediation efforts. The continuous monitoring feature can alert you when new SSRF vulnerabilities are introduced in your codebase.
Adonisjs-Specific Remediation
Remediating SSRF vulnerabilities in Adonisjs requires a defense-in-depth approach. The framework provides several mechanisms to implement proper controls.
First, implement allowlist-based URL validation using Adonisjs's validation system:
const { validate } = use('Validator');
async processUrl({ request }) {
const rules = {
target_url: 'required|url',
// Custom validation to check allowed domains
allowed_domain: 'in:example.com,yourservice.com'
};
const validation = await validate(request.all(), rules);
if (validation.fails()) {
return {
success: false,
message: 'Invalid or unauthorized URL'
};
}
// Additional runtime check
const url = new URL(request.input('target_url'));
const allowedDomains = ['example.com', 'yourservice.com'];
if (!allowedDomains.includes(url.hostname)) {
throw new Error('URL not in allowlist');
}
const response = await Http.get(request.input('target_url'));
return response.body();
}For Adonisjs applications that must make outbound requests to various domains, implement a domain allowlist with configurable exceptions:
class SafeHttpClient {
constructor() {
this.allowedDomains = Config.get('security.allowed_domains', []);
this.blockedPatterns = Config.get('security.blocked_patterns', []);
}
async safeGet(url) {
const parsedUrl = new URL(url);
// Check allowlist
if (this.allowedDomains.length > 0 &&
!this.allowedDomains.includes(parsedUrl.hostname)) {
throw new Error(`Domain ${parsedUrl.hostname} not allowed`);
}
// Check blocked patterns (private IPs, localhost, etc.)
const blockedPatterns = [
/^localhost$/,
/^127(?:\.[0-9]+){0,2}$/,
/^(10|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\./,
/^169\.254\.169\.254$/,
/^0\.0\.0\.0$/
];
if (this.blockedPatterns.length > 0) {
const blocked = this.blockedPatterns.some(pattern =>
pattern.test(parsedUrl.hostname)
);
if (blocked) {
throw new Error('Blocked URL pattern detected');
}
}
// Use Adonisjs HTTP client with timeout
const response = await Http.get(url, {
timeout: 5000, // 5 second timeout
headers: {
'User-Agent': 'SafeHttpClient/1.0'
}
});
return response.body();
}
}Adonisjs middleware can enforce SSRF protection globally:
class SSRFProtection {
async handle({ request, response }, next) {
const urlParams = ['url', 'callback', 'redirect', 'target', 'file_url'];
for (const param of urlParams) {
const value = request.input(param);
if (value && typeof value === 'string') {
try {
const url = new URL(value);
if (this.isInternalNetwork(url)) {
return response.status(400).json({
error: 'URL points to internal network'
});
}
} catch (error) {
// Invalid URL, continue
}
}
}
await next();
}
isInternalNetwork(url) {
const privateIPRanges = [
/^127(?:\.[0-9]+){0,2}$/,
/^(10|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\./,
/^169\.254\.169\.254$/,
/^0\.0\.0\.0$/
];
return privateIPRanges.some(pattern => pattern.test(url.hostname));
}
}Register this middleware in start/kernel.js:
const globalMiddleware = [
'Adonis/Middleware/SSRProtection'
];For Adonisjs applications using external service integrations, create a dedicated service layer with built-in SSRF protection:
class ExternalServiceManager {
constructor() {
this.whitelistedServices = new Set([
'api.example.com',
'service.yoursite.com',
'cdn.provider.net'
]);
}
async callService(endpoint, data = {}) {
const baseUrl = Config.get('services.external.base_url');
const fullUrl = `${baseUrl}${endpoint}`;
const url = new URL(fullUrl);
if (!this.whitelistedServices.has(url.hostname)) {
throw new Error(`Service ${url.hostname} not authorized`);
}
try {
const response = await Http.post(fullUrl, data, {
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${Config.get('services.external.token')}`
}
});
return response.body();
} catch (error) {
// Log without exposing sensitive data
Logger.warn(`External service call failed: ${error.message}`);
throw new Error('Service temporarily unavailable');
}
}
}Finally, implement comprehensive logging and monitoring for outbound requests in your Adonisjs application to detect suspicious patterns:
class RequestLogger {
async handle({ request }, next) {
const startTime = Date.now();
await next();
const responseTime = Date.now() - startTime;
const url = request.url();
// Log external requests for audit
if (url.startsWith('/api/external') || url.includes('callback')) {
Logger.info(`External request: ${url} - ${responseTime}ms`);
}
}
}Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |