Ssrf with Mutual Tls
How SSRF Manifests in Mutual TLS
Server-Side Request Forgery (SSRF) in Mutual TLS environments presents unique challenges because the attacker must bypass both authentication and mutual certificate validation. In Mutual TLS (mTLS), both client and server present certificates, creating an additional attack surface when SSRF vulnerabilities exist.
The most common SSRF pattern in mTLS APIs occurs when an endpoint accepts a URL parameter and makes an internal request using the server's mTLS client certificate. Consider this vulnerable Node.js Express endpoint:
app.post('/api/forward', (req, res) => {
const targetUrl = req.body.targetUrl;
https.get(targetUrl, (response) => {
let data = '';
response.on('data', chunk => data += chunk);
response.on('end', () => res.json(JSON.parse(data)));
});
});This code is vulnerable because it trusts the client-provided URL and uses the server's mTLS context to make requests. An attacker could supply https://internal-service.example.com:8443/api/sensitive and the server would use its mTLS certificate to authenticate with the internal service.
Another mTLS-specific SSRF variant involves certificate path manipulation. When an API validates certificates but fails to properly validate the entire certificate chain, attackers can exploit this by:
- Targeting internal services that expect mTLS but have weak certificate validation
- Manipulating the
Hostheader to bypass virtual host filtering - Exploiting DNS rebinding to make internal services appear external
In Java Spring Boot applications using mTLS, the vulnerability often manifests in Feign clients or RestTemplate configurations:
@Component
public class ForwardService {
@Autowired
private RestTemplate restTemplate;
public String forwardRequest(String url) {
return restTemplate.getForObject(url, String.class);
}
}The vulnerability here is that the RestTemplate inherits the default SSLContext configured for mTLS, automatically authenticating with any internal service the attacker targets.
Mutual TLS-Specific Detection
Detecting SSRF in mTLS environments requires specialized scanning that understands certificate-based authentication flows. Traditional SSRF scanners miss mTLS-specific vulnerabilities because they don't test certificate validation paths.
middleBrick's mTLS-aware scanning tests SSRF by:
- Attempting to access internal mTLS-protected endpoints using the target's certificate context
- Testing certificate path validation bypasses by manipulating request headers and DNS resolution
- Scanning for mTLS endpoints that accept arbitrary URLs without proper validation
For manual detection, look for these patterns in your mTLS API code:
// Vulnerable: accepts arbitrary URLs
public String proxyRequest(@RequestParam String url) {
return restTemplate.getForObject(url, String.class);
}
// Vulnerable: missing URL validation
public void processExternalData(String externalApiUrl) {
restTemplate.exchange(externalApiUrl, HttpMethod.GET, null, String.class);
}Effective detection also involves network-level monitoring. Watch for outbound mTLS connections from your API servers to unexpected internal IPs or hostnames. Tools like Wireshark or tcpdump can reveal these patterns:
tcpdump -i any -nnvvXSs 1514 'tcp port 443 and host 10.0.0.0/8'middleBrick specifically tests for mTLS SSRF by attempting connections to common internal service ports (443, 8443, 8080, 9443) and validating whether the target's mTLS configuration allows unauthorized internal access.
Mutual TLS-Specific Remediation
Remediating SSRF in mTLS environments requires a defense-in-depth approach that addresses both the URL validation and certificate validation aspects. The most effective strategy combines strict URL filtering with mTLS-specific controls.
Start with strict URL validation using a whitelist approach:
public class SecureUrlValidator {
private static final Pattern ALLOWED_PATTERN = Pattern.compile(
"^https://(api\.example\.com|service\.example\.com)(:\d+)?(/.*)?$");
public static boolean isValid(String url) {
try {
URI uri = new URI(url);
if (!"https".equals(uri.getScheme())) return false;
if (!ALLOWED_PATTERN.matcher(uri.toString()).matches()) return false;
// Additional mTLS-specific check
if (isInternalService(uri.getHost())) {
throw new SecurityException("Internal service access denied");
}
return true;
} catch (URISyntaxException e) {
return false;
}
}
private static boolean isInternalService(String host) {
// Check against internal IP ranges and hostnames
return host.matches(".*\\.internal");
}
}For mTLS-specific hardening, implement certificate pinning at the application layer:
@Configuration
public class MTLSClientConfig {
@Bean
public RestTemplate restTemplate() throws Exception {
RestTemplate restTemplate = new RestTemplate();
// Create SSLContext with certificate pinning
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, new SecureRandom());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
sslContext,
new String[] {"TLSv1.2", "TLSv1.3"},
null,
new DefaultHostnameVerifier() {
@Override
public void verify(String host, SSLSession session) {
// Custom verification that rejects internal services
if (isInternalService(host)) {
throw new SSLHandshakeException("Internal service blocked");
}
super.verify(host, session);
}
}
);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
return restTemplate;
}
}Another critical mTLS-specific remediation is implementing network segmentation and firewall rules that prevent your API servers from making mTLS connections to internal services unless explicitly authorized. This creates a second layer of defense beyond application-level validation.
For Node.js applications using mTLS, implement similar protections:
const https = require('https');
const url = require('url');
function validateUrl(targetUrl) {
const parsed = new URL(targetUrl);
// Whitelist allowed domains
const allowedDomains = ['api.example.com', 'service.example.com'];
if (!allowedDomains.includes(parsed.hostname)) {
throw new Error('URL not allowed');
}
// Block internal network ranges
const internalNetworks = [
/^10\./,
/^192\.168\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./
];
if (internalNetworks.some(regex => regex.test(parsed.hostname))) {
throw new Error('Internal network access blocked');
}
return true;
}
function secureForwardRequest(targetUrl) {
validateUrl(targetUrl);
return new Promise((resolve, reject) => {
https.get(targetUrl, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
});
}Related CWEs: ssrf
| CWE ID | Name | Severity |
|---|---|---|
| CWE-918 | Server-Side Request Forgery (SSRF) | CRITICAL |
| CWE-441 | Unintended Proxy or Intermediary (Confused Deputy) | HIGH |