flawopen.com/Server-Side Request Forgery/Java
Your Java server is asked to download an invoice logo. The link is 'http://169.254.169.254/secret'. The Java program connects, downloads your cloud keys, and sends them back to the user.
In Java, new URL(url).openStream() or naive HttpClient usage fetches arbitrary network destinations. Internal services like Actuator endpoints (/actuator/env) or IMDS are exposed to callers.
In 2021, severe SSRF vulnerabilities in enterprise Java middleware (Oracle WebLogic and Apache Struts) allowed unauthenticated attackers to query internal database listeners.
CISA Cybersecurity Advisory & MITRE CVE repository.// VULNERABLE: Naive java.net.URL openStream()
import java.net.URL;
import java.io.InputStream;
import javax.servlet.http.*;
public class ProxyServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String targetUrl = req.getParameter("url");
// Attacker input: http://169.254.169.254/latest/meta-data/
try {
URL url = new URL(targetUrl);
InputStream in = url.openStream();
in.transferTo(resp.getOutputStream());
} catch (Exception e) {
resp.setStatus(500);
}
}
}
// HARDENED: Resolve all IPs, assert non-private, and disable redirects
import java.net.*;
import java.net.http.*;
import java.time.Duration;
import javax.servlet.http.*;
public class ProxyServlet extends HttpServlet {
private final HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
private boolean isSafeAddress(InetAddress addr) {
return !addr.isLoopbackAddress() &&
!addr.isSiteLocalAddress() &&
!addr.isLinkLocalAddress() &&
!addr.isAnyLocalAddress();
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
String targetUrl = req.getParameter("url");
try {
URI uri = URI.create(targetUrl);
if (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https")) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid scheme");
return;
}
// 1. Resolve and validate all IP addresses
InetAddress[] addresses = InetAddress.getAllByName(uri.getHost());
for (InetAddress addr : addresses) {
if (!isSafeAddress(addr)) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access to internal IP prohibited");
return;
}
}
// 2. Fetch using hardened client
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(4))
.GET()
.build();
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
resp.getOutputStream().write(response.body());
} catch (Exception e) {
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
}
The code uses InetAddress.getAllByName() to inspect all IPs associated with the host. isSiteLocalAddress() and isLinkLocalAddress() block internal VPC subnets and cloud metadata. followRedirects(Redirect.NEVER) blocks 302 bypasses.
Java's java.net.URL.equals() triggers a blocking DNS lookup. Always use java.net.URI for representation and validation.
If the allowed domain permits open redirects, or if DNS records point to internal addresses, the check is bypassed.
spotbugs -textui -include spotbugs-security.xml .
java.net.http.HttpClient with Redirect.NEVERInetAddress.getAllByName()!addr.isSiteLocalAddress() && !addr.isLinkLocalAddress()Link-local (169.254.0.0/16) is flagged by addr.isLinkLocalAddress(). Blocking this range protects against AWS, Azure, and GCP metadata exfiltration.