mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-25 21:59:23 +00:00
feat(ssrf): enhance private IP detection and IPv6 handling (#4191)
# Description of Changes - Refactored `isPrivateAddress` to improve detection of private and local addresses for both IPv4 and IPv6. - Added explicit handling for: - IPv4-mapped IPv6 addresses - IPv6 link-local, site-local, and unique local (fc00::/7) addresses - Additional IPv4 private ranges such as link-local (169.254.0.0/16) - Introduced `normalizeIpv4MappedAddress` to standardize IP checks in cloud metadata detection. - Replaced `switch` statement with modern `switch` expression for cleaner control flow. These changes were made to strengthen SSRF protection by covering more address edge cases, especially in mixed IPv4/IPv6 environments. This also improves detection of cloud metadata endpoints when accessed via IPv4-mapped IPv6 addresses. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
f5f011f1e0
commit
40cf337b23
@ -1,5 +1,7 @@
|
||||
package stirling.software.common.service;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
@ -51,16 +53,12 @@ public class SsrfProtectionService {
|
||||
|
||||
SsrfProtectionLevel level = parseProtectionLevel(config.getLevel());
|
||||
|
||||
switch (level) {
|
||||
case OFF:
|
||||
return true;
|
||||
case MAX:
|
||||
return isMaxSecurityAllowed(trimmedUrl, config);
|
||||
case MEDIUM:
|
||||
return isMediumSecurityAllowed(trimmedUrl, config);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return switch (level) {
|
||||
case OFF -> true;
|
||||
case MAX -> isMaxSecurityAllowed(trimmedUrl, config);
|
||||
case MEDIUM -> isMediumSecurityAllowed(trimmedUrl, config);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private SsrfProtectionLevel parseProtectionLevel(String level) {
|
||||
@ -172,15 +170,61 @@ public class SsrfProtectionService {
|
||||
}
|
||||
|
||||
private boolean isPrivateAddress(InetAddress address) {
|
||||
return address.isSiteLocalAddress()
|
||||
|| address.isAnyLocalAddress()
|
||||
|| isPrivateIPv4Range(address.getHostAddress());
|
||||
if (address.isAnyLocalAddress() || address.isLoopbackAddress()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (address instanceof Inet4Address) {
|
||||
return isPrivateIPv4Range(address.getHostAddress());
|
||||
}
|
||||
|
||||
if (address instanceof Inet6Address addr6) {
|
||||
if (addr6.isLinkLocalAddress() || addr6.isSiteLocalAddress()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] bytes = addr6.getAddress();
|
||||
if (isIpv4MappedAddress(bytes)) {
|
||||
String ipv4 =
|
||||
(bytes[12] & 0xff)
|
||||
+ "."
|
||||
+ (bytes[13] & 0xff)
|
||||
+ "."
|
||||
+ (bytes[14] & 0xff)
|
||||
+ "."
|
||||
+ (bytes[15] & 0xff);
|
||||
return isPrivateIPv4Range(ipv4);
|
||||
}
|
||||
|
||||
int firstByte = bytes[0] & 0xff;
|
||||
// Check for IPv6 unique local addresses (fc00::/7)
|
||||
if ((firstByte & 0xfe) == 0xfc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isIpv4MappedAddress(byte[] addr) {
|
||||
if (addr.length != 16) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (addr[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// For IPv4-mapped IPv6 addresses, bytes 10 and 11 must be 0xff (i.e., address is ::ffff:w.x.y.z)
|
||||
return addr[10] == (byte) 0xff && addr[11] == (byte) 0xff;
|
||||
}
|
||||
|
||||
private boolean isPrivateIPv4Range(String ip) {
|
||||
// Includes RFC1918, loopback, link-local, and unspecified addresses
|
||||
return ip.startsWith("10.")
|
||||
|| ip.startsWith("192.168.")
|
||||
|| (ip.startsWith("172.") && isInRange172(ip))
|
||||
|| ip.startsWith("169.254.")
|
||||
|| ip.startsWith("127.")
|
||||
|| "0.0.0.0".equals(ip);
|
||||
}
|
||||
@ -192,17 +236,31 @@ public class SsrfProtectionService {
|
||||
int secondOctet = Integer.parseInt(parts[1]);
|
||||
return secondOctet >= 16 && secondOctet <= 31;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCloudMetadataAddress(String ip) {
|
||||
String normalizedIp = normalizeIpv4MappedAddress(ip);
|
||||
// Cloud metadata endpoints for AWS, GCP, Azure, Oracle Cloud, and IBM Cloud
|
||||
return ip.startsWith("169.254.169.254") // AWS/GCP/Azure
|
||||
|| ip.startsWith("fd00:ec2::254") // AWS IPv6
|
||||
|| ip.startsWith("169.254.169.253") // Oracle Cloud
|
||||
|| ip.startsWith("169.254.169.250"); // IBM Cloud
|
||||
return normalizedIp.startsWith("169.254.169.254") // AWS/GCP/Azure
|
||||
|| normalizedIp.startsWith("fd00:ec2::254") // AWS IPv6
|
||||
|| normalizedIp.startsWith("169.254.169.253") // Oracle Cloud
|
||||
|| normalizedIp.startsWith("169.254.169.250"); // IBM Cloud
|
||||
}
|
||||
|
||||
private String normalizeIpv4MappedAddress(String ip) {
|
||||
if (ip == null) {
|
||||
return "";
|
||||
}
|
||||
if (ip.startsWith("::ffff:")) {
|
||||
return ip.substring(7);
|
||||
}
|
||||
int lastColon = ip.lastIndexOf(':');
|
||||
if (lastColon >= 0 && ip.indexOf('.') > lastColon) {
|
||||
return ip.substring(lastColon + 1);
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user