flawopen.com/ssrf/Java
How java.net.URL and HttpClient allow internal infrastructure scanning, and how to enforce IP address boundaries in Java.
Bayangkan mengirim kurir kantor untuk mengambil paket di toko umum, tetapi memberinya alamat brankas terkunci di ruang manajer. Karena kurir memiliki lencana akses internal, dia membuka brankas dan menyerahkan rahasia perusahaan kepada Anda.
Web Application SecurityCWE-918.CWE-918Defense-in-DepthAplikasi menerima parameter URL dari pengguna untuk mengunduh avatar, webhook, atau pratinjau dokumen.
Penyerang memasukkan alamat loopback atau layanan metadata cloud (misal: http://169.254.169.254/latest/meta-data/).
Klien HTTP server mengirim permintaan dari dalam VPC privat tanpa memeriksa rentang subnet privat.
Layanan metadata internal mempercayai permintaan dan mengembalikan token kredensial IAM sementara ke penyerang.
// 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().