CWE-918 in APIs
What is CWE-918?
CWE-918 (Server-Side Request Forgery - SSRF) is a critical web application vulnerability where an attacker can coerce a server into making unauthorized requests to internal or external resources. The weakness occurs when an application uses user-supplied URLs to make HTTP requests without proper validation or authorization.
The core problem: the server trusts its own network position and doesn't verify whether it should be accessing the requested resource. An attacker can leverage this trust to reach internal systems, bypass network controls, or access sensitive data.
According to the CWE description, this weakness exists when "the software does not properly control the redirection of a request to another location." This creates a scenario where malicious actors can manipulate URLs to target internal APIs, cloud metadata services, or external systems.
CWE-918 in API Contexts
APIs are particularly vulnerable to SSRF because they often need to make outbound requests as part of their functionality. Common API patterns that introduce SSRF risk include:
- Webhook processing - accepting URLs where events should be sent
- API aggregation - fetching data from user-provided endpoints
- Image processing - downloading images from URLs for analysis
- PDF generation - rendering web pages from URLs
- Import/export functionality - reading data from external sources
- Payment processing - communicating with external payment gateways
In API contexts, SSRF becomes more dangerous because APIs often have privileged network access. An API server might be able to reach internal databases, admin interfaces, or cloud provider metadata endpoints that contain credentials or configuration data.
Consider an API endpoint that accepts a URL parameter to fetch user profile pictures. If an attacker provides http://169.254.169.254/latest/meta-data/ (AWS metadata service), the API server might return sensitive instance information including IAM role credentials.
Detection
Detecting SSRF requires both static analysis and dynamic testing. Here's how to identify this weakness:
- Code review - Look for HTTP client usage with user-supplied URLs, lack of URL validation, and absence of allowlist patterns
- Network monitoring - Analyze outbound requests for unexpected destinations or patterns
- Automated scanning - Use tools that can detect SSRF vulnerabilities by attempting controlled requests to known internal services
middleBrick's SSRF detection specifically tests for this weakness by attempting to access common internal endpoints and cloud metadata services. The scanner tries requests to:
- Private IP ranges (10.x, 172.16-31.x, 192.168.x)
- Localhost and loopback addresses
- Cloud metadata endpoints (AWS, Azure, GCP)
- Common internal service ports
The scanner reports whether the API server responds to these requests, indicating SSRF vulnerability. This black-box approach tests the actual runtime behavior without requiring source code access.
middleBrick also checks for proper URL validation by attempting protocol smuggling (file://, gopher://, ftp://) and testing whether the API properly handles different URL schemes.
Remediation
Fixing SSRF requires a defense-in-depth approach with multiple layers of validation:
1. URL Validation and Allowlisting
const validateUrl = (inputUrl, allowedDomains) => {
try {
const url = new URL(inputUrl);
// Protocol allowlist
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('Invalid protocol');
}
// Domain allowlist check
const normalizedDomain = url.hostname.toLowerCase();
if (!allowedDomains.includes(normalizedDomain)) {
throw new Error('Domain not allowed');
}
// Private IP range detection
const ip = url.hostname.split('.').map(Number);
if (ip.length === 4) {
if (
(ip[0] === 10) ||
(ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) ||
(ip[0] === 192 && ip[1] === 168) ||
(ip[0] === 127) ||
(ip[0] === 169 && ip[1] === 254)
) {
throw new Error('Private IP address not allowed');
}
}
return url;
} catch (error) {
throw new Error(`Invalid URL: ${error.message}`);
}
};
// Usage
const allowedDomains = ['trusted-api.com', 'cdn.example.com'];
const safeUrl = validateUrl(userProvidedUrl, allowedDomains);
2. Network-Level Controls
Implement network controls to prevent outbound requests to restricted destinations:
# iptables rules to block internal network access
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP
iptables -A OUTPUT -d 172.16.0.0/12 -j DROP
iptables -A OUTPUT -d 192.168.0.0/16 -j DROP
iptables -A OUTPUT -d 127.0.0.0/8 -j DROP
iptables -A OUTPUT -d 169.254.0.0/16 -j DROP
3. Timeouts and Resource Limits
Always implement strict timeouts and resource limits:
const fetchWithLimits = async (url) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout
try {
const response = await fetch(url, {
signal: controller.signal,
headers: { 'User-Agent': 'YourAppName/1.0' },
compress: false
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.text();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timeout');
}
throw error;
} finally {
clearTimeout(timeout);
}
};
4. Content-Type Validation
Validate that responses match expected content types:
const validateContentType = async (url, expectedType) => {
const response = await fetch(url, { method: 'HEAD' });
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.startsWith(expectedType)) {
throw new Error('Unexpected content type');
}
return response;
};
For comprehensive protection, combine these approaches. Use allowlisting for known-good domains, network controls for defense-in-depth, and always implement timeouts and content validation.