flawopen.com/ssrf/Java
How java.net.URL and HttpClient allow internal infrastructure scanning, and how to enforce IP address boundaries in Java.
Imaginez envoyer un coursier chercher un colis dans une boutique publique, mais en lui donnant l'adresse du coffre-fort du bureau de la direction. Comme le coursier possède un badge d'accès interne, il ouvre le coffre et vous remet les secrets de l'entreprise.
Web Application SecurityCWE-918.CWE-918Defense-in-DepthL'application prend en charge une URL externe pour récupérer un avatar, un webhook ou convertir un document.
L'attaquant fournit une URL ciblant les métadonnées cloud (ex. http://169.254.169.254/latest/meta-data/) ou une IP privée de bouclage.
Le client HTTP du serveur émet une requête depuis le réseau privé sans résoudre au préalable l'adresse IP cible.
Le service de métadonnées interne fait confiance au serveur et renvoie les clés IAM temporaires ou les jetons d'accès.
// 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);
}
}
}
InetAddress.getAllByName().addr.isSiteLocalAddress() && !addr.isLinkLocalAddress().