Skip to content

Instantly share code, notes, and snippets.

@sachin-handiekar
Created September 24, 2025 13:54
Show Gist options
  • Save sachin-handiekar/8582d43aa09b068c42e55cdf2a1a25e6 to your computer and use it in GitHub Desktop.
Save sachin-handiekar/8582d43aa09b068c42e55cdf2a1a25e6 to your computer and use it in GitHub Desktop.
Test Code SSRF
package com.interview.practice.ssrf;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientProperties;
import java.net.IDN;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* Demonstrates an SSRF-prone usage of JAX-RS WebTarget and a secured variant.
*/
public class WebTargetSsrpExample {
private final Set<String> allowedHosts;
public WebTargetSsrpExample(Set<String> allowedHosts) {
this.allowedHosts = Collections.unmodifiableSet(new HashSet<>(Objects.requireNonNull(allowedHosts)));
}
/**
* Example of SSRF-vulnerable code: directly uses user-controlled URL.
*/
public String fetchVulnerable(String userProvidedUrl) {
Client client = ClientBuilder.newClient();
try {
WebTarget target = client.target(userProvidedUrl);
return target.request().get(String.class);
} finally {
client.close();
}
}
/**
* Secure alternative: strict validation, allow-listing, and safe client config.
*/
public String fetchSecure(String userProvidedUrl) {
URI safeUri = validateAndNormalize(userProvidedUrl)
.filter(this::isAllowedHost)
.filter(uri -> !isPrivateOrLocalAddress(uri))
.orElseThrow(() -> new IllegalArgumentException("Blocked URL by SSRF protection"));
Client client = ClientBuilder.newBuilder()
// Disallow following redirects to avoid redirect-to-internal SSRF
.property(ClientProperties.FOLLOW_REDIRECTS, false)
// Reasonable timeouts
.property(ClientProperties.CONNECT_TIMEOUT, (int) Duration.ofSeconds(5).toMillis())
.property(ClientProperties.READ_TIMEOUT, (int) Duration.ofSeconds(10).toMillis())
.build();
try {
WebTarget target = client.target(safeUri);
Response response = target.request()
.header("User-Agent", "SSRF-Example-Client/1.0")
.get();
try {
if (response.getStatus() >= 200 && response.getStatus() < 300) {
return response.readEntity(String.class);
}
throw new ProcessingException("Upstream call failed: HTTP " + response.getStatus());
} finally {
response.close();
}
} finally {
client.close();
}
}
private Optional<URI> validateAndNormalize(String input) {
try {
if (input == null || input.isBlank()) return Optional.empty();
// Basic parse and normalization
URI uri = new URI(input.trim());
if (uri.getScheme() == null) return Optional.empty();
String scheme = uri.getScheme().toLowerCase();
if (!scheme.equals("http") && !scheme.equals("https")) return Optional.empty();
// No credentials in authority
String userInfo = uri.getUserInfo();
if (userInfo != null) return Optional.empty();
// Normalize host using IDNA to avoid homograph tricks
String host = uri.getHost();
if (host == null || host.isBlank()) return Optional.empty();
String asciiHost = IDN.toASCII(host);
int port = uri.getPort();
if (port == 0 || port < -1 || port > 65535) return Optional.empty();
// Rebuild a clean URI (drop fragments)
URI clean = new URI(
scheme,
null,
asciiHost,
port,
uri.getPath() == null || uri.getPath().isBlank() ? "/" : uri.getPath(),
uri.getQuery(),
null
).normalize();
return Optional.of(clean);
} catch (URISyntaxException e) {
return Optional.empty();
}
}
private boolean isAllowedHost(URI uri) {
String host = uri.getHost();
if (host == null) return false;
String asciiHost = IDN.toASCII(host).toLowerCase();
// Exact allow-list match OR subdomain of an allowed host
for (String allowed : allowedHosts) {
String a = IDN.toASCII(allowed).toLowerCase();
if (asciiHost.equals(a) || asciiHost.endsWith("." + a)) {
return true;
}
}
return false;
}
private boolean isPrivateOrLocalAddress(URI uri) {
try {
String host = uri.getHost();
if (host == null) return true;
// If host is a literal IP, check directly; otherwise resolve A/AAAA and check all
InetAddress[] addrs = InetAddress.getAllByName(host);
for (InetAddress addr : addrs) {
if (addr.isAnyLocalAddress() ||
addr.isLoopbackAddress() ||
addr.isLinkLocalAddress() ||
addr.isSiteLocalAddress() ||
addr.isMulticastAddress()) {
return true;
}
byte[] bytes = addr.getAddress();
// 169.254.0.0/16 (link-local)
if ((bytes[0] & 0xFF) == 169 && (bytes[1] & 0xFF) == 254) return true;
// 100.64.0.0/10 (CGNAT)
int first = bytes[0] & 0xFF;
int second = bytes[1] & 0xFF;
if (first == 100 && (second >= 64 && second <= 127)) return true;
}
return false;
} catch (UnknownHostException e) {
// If DNS fails, treat as unsafe
return true;
}
}
public static void main(String[] args) {
// Example usage
WebTargetSsrpExample example = new WebTargetSsrpExample(Set.of("api.example.com"));
String vulnerableUrl = "http://127.0.0.1:8000/admin"; // This would be attempted in vulnerable flow
String safeUrl = "https://api.example.com/v1/health"; // Allowed by allow-list
try {
System.out.println("Vulnerable call result (may SSRF): " + example.fetchVulnerable(vulnerableUrl));
} catch (Exception ex) {
System.out.println("Vulnerable call failed: " + ex.getMessage());
}
try {
System.out.println("Secure call result: " + example.fetchSecure(safeUrl));
} catch (Exception ex) {
System.out.println("Secure call blocked or failed: " + ex.getMessage());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment