Created
September 24, 2025 13:54
-
-
Save sachin-handiekar/8582d43aa09b068c42e55cdf2a1a25e6 to your computer and use it in GitHub Desktop.
Test Code SSRF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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