Ssrf in Feathersjs
How SSRF Manifests in Feathersjs
Server-Side Request Forgery (SSRF) in Feathersjs applications often emerges from seemingly innocuous patterns. The framework's flexibility with services, hooks, and REST endpoints creates multiple attack surfaces that developers might overlook.
A common manifestation occurs when Feathersjs services accept URLs as parameters and make HTTP requests based on user input. Consider a Feathersjs service that proxies external API calls:
class ProxyService extends Service {
async find(params) {
const url = params.query.url;
return fetch(url).then(res => res.json());
}
}
app.use('/proxy', new ProxyService());
This pattern appears frequently in Feathersjs applications for legitimate purposes—proxying third-party APIs, fetching remote resources, or integrating with microservices. However, it creates a critical vulnerability when the URL parameter isn't validated.
Another Feathersjs-specific scenario involves service discovery and inter-service communication. Feathersjs applications often use app.service('service-name').find() to call internal services, but this can be abused when combined with dynamic service resolution:
const serviceName = params.query.service;
const result = await app.service(serviceName).find(params);
Attackers can manipulate the service parameter to access internal services they shouldn't have access to, potentially exposing sensitive data or triggering unintended operations within your Feathersjs application.
Feathersjs's hook system can also introduce SSRF-like vulnerabilities. Hooks that process URLs from external sources without proper validation create attack opportunities:
const processHook = async (context) => {
const externalUrl = context.data.externalUrl;
const response = await fetch(externalUrl);
context.data = { ...context.data, processed: response.text };
return context;
};
The framework's real-time capabilities through Socket.io or Primus can exacerbate SSRF risks when URL parameters are passed through WebSocket connections without the same validation rigor applied to REST endpoints.
Feathersjs-Specific Detection
Detecting SSRF vulnerabilities in Feathersjs applications requires examining both the code structure and runtime behavior. The framework's modular architecture means vulnerabilities can be scattered across services, hooks, and configuration files.
Static code analysis should focus on patterns where Feathersjs services accept URL parameters and make outbound requests. Look for:
- Services that use
fetch(),axios, or other HTTP clients with user-supplied URLs - Dynamic service resolution using
app.service()with user-controlled parameters - Hooks that process external URLs without validation
- Configuration files that might expose internal service endpoints
Runtime detection with middleBrick provides comprehensive coverage for Feathersjs applications. The scanner examines your API endpoints for SSRF vulnerabilities by testing common attack patterns:
npm install -g middlebrick
middlebrick scan https://your-feathersjs-app.com/api
middleBrick's SSRF detection specifically tests for:
- URL parameter injection attempts
- Internal network access attempts (192.168.x.x, 10.x.x.x, 172.16.x.x)
- Cloud metadata service access (169.254.169.254)
- Loopback interface access (127.0.0.1, localhost)
- Protocol smuggling attempts (file://, gopher://, ftp://)
For Feathersjs applications using TypeScript, middleBrick can analyze your compiled JavaScript to identify SSRF-prone patterns even if the source code isn't available. The scanner's OpenAPI analysis also examines your Feathersjs-generated API documentation to identify endpoints that might be vulnerable.
middleBrick's LLM security scanning is particularly relevant for Feathersjs applications using AI features, as SSRF attacks can target AI model endpoints or prompt injection systems integrated into your Feathersjs services.
Feathersjs-Specific Remediation
Remediating SSRF vulnerabilities in Feathersjs applications requires a defense-in-depth approach. Start with input validation and sanitization at the service level:
const allowedDomains = ['api.example.com', 'api.partner.com'];
class SecureProxyService extends Service {
async find(params) {
const url = params.query.url;
// Validate URL scheme
const urlObj = new URL(url);
if (!['http:', 'https:'].includes(urlObj.protocol)) {
throw new errors.BadRequest('Invalid protocol');
}
// Check against allowlist
if (!allowedDomains.includes(urlObj.hostname)) {
throw new errors.Forbidden('Domain not allowed');
}
// Additional validation for internal networks
if (this.isPrivateIP(urlObj.hostname)) {
throw new errors.Forbidden('Private IP access denied');
}
return fetch(url).then(res => res.json());
}
isPrivateIP(hostname) {
const ip = hostname.match(/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)·){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/);
if (!ip) return false;
const octets = ip[0].split('.').map(Number);
return (
(octets[0] === 10) ||
(octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) ||
(octets[0] === 192 && octets[1] === 168) ||
(octets[0] === 127)
);
}
}
For Feathersjs applications, implement centralized URL validation using hooks. Create a reusable hook that validates all external requests:
const urlValidationHook = async (context) => {
const urlParams = ['url', 'callbackUrl', 'webhookUrl'];
for (const param of urlParams) {
if (context.data[param] || context.params.query[param]) {
const url = context.data[param] || context.params.query[param];
const urlObj = new URL(url);
// Validate protocol
if (!['http:', 'https:'].includes(urlObj.protocol)) {
throw new errors.BadRequest('Invalid protocol');
}
// Check for private IP ranges
if (this.isPrivateIP(urlObj.hostname)) {
throw new errors.Forbidden('Private network access denied');
}
// Check against allowlist if configured
if (this.allowedDomains && !this.allowedDomains.includes(urlObj.hostname)) {
throw new errors.Forbidden('Domain not allowed');
}
}
}
return context;
};
// Apply globally to services that handle URLs
app.service('proxy').hooks({
before: {
find: [urlValidationHook]
}
});
For Feathersjs applications using TypeScript, leverage type safety to prevent SSRF vulnerabilities:
interface ValidUrl {
url: string;
isValid: boolean;
reason?: string;
}
function validateUrl(url: string): ValidUrl {
try {
const urlObj = new URL(url);
if (!['http:', 'https:'].includes(urlObj.protocol)) {
return { url, isValid: false, reason: 'Invalid protocol' };
}
const privateIP = /^(10\u00b7|172·(1[6-9]|2[0-9]|3[0-1])·|192·168·|127·)/.test(urlObj.hostname);
if (privateIP) {
return { url, isValid: false, reason: 'Private IP blocked' };
}
return { url, isValid: true };
} catch (error) {
return { url, isValid: false, reason: 'Invalid URL format' };
}
}
Implement rate limiting and monitoring for services that make external requests. Feathersjs's ecosystem includes middleware that can track unusual patterns indicative of SSRF attacks:
const rateLimit = require('express-rate-limit');
const requestLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use(requestLimiter);
Consider using a dedicated HTTP client with built-in SSRF protection for Feathersjs services that need to make external requests:
const safeFetch = require('safe-fetch');
safeFetch('http://example.com', {
// safe-fetch blocks private IPs, invalid protocols, etc.
});
Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |