From 3faa35f62f61ab344227126bbc65addd6ddb3a4f Mon Sep 17 00:00:00 2001 From: zane-neo Date: Wed, 12 Jul 2023 06:47:47 +0800 Subject: [PATCH] Fix rare private ip address bypass SSRF issue (#1070) * Change connector access control creation allow empty list Signed-off-by: zane-neo * Fix rare private ip address bypass SSRF issue Signed-off-by: zane-neo --------- Signed-off-by: zane-neo (cherry picked from commit e3cb2e3a608f8c9b0174551817f6ed0ba01b8021) --- .../httpclient/MLHttpClientFactory.java | 44 ++++++++++++++++--- .../httpclient/MLHttpClientFactoryTests.java | 41 +++++++++++++++++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/ml-algorithms/src/main/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactory.java b/ml-algorithms/src/main/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactory.java index 4b1617e7af..782bd9501e 100644 --- a/ml-algorithms/src/main/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactory.java +++ b/ml-algorithms/src/main/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactory.java @@ -19,6 +19,7 @@ import org.apache.http.protocol.HttpContext; import org.apache.logging.log4j.util.Strings; +import java.net.Inet4Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; @@ -42,10 +43,7 @@ public int resolve(HttpHost host) throws UnsupportedSchemeException { } }); - builder.setDnsResolver(hostName -> { - validateIp(hostName); - return InetAddress.getAllByName(hostName); - }); + builder.setDnsResolver(MLHttpClientFactory::validateIp); builder.setRedirectStrategy(new LaxRedirectStrategy() { @Override @@ -79,15 +77,51 @@ protected static void validateSchemaAndPort(HttpHost host) { } } - protected static void validateIp(String hostName) throws UnknownHostException { + protected static InetAddress[] validateIp(String hostName) throws UnknownHostException { InetAddress[] addresses = InetAddress.getAllByName(hostName); if (hasPrivateIpAddress(addresses)) { log.error("Remote inference host name has private ip address: " + hostName); throw new IllegalArgumentException(hostName); } + return addresses; } private static boolean hasPrivateIpAddress(InetAddress[] ipAddress) { + for (InetAddress ip : ipAddress) { + if (ip instanceof Inet4Address) { + byte[] bytes = ip.getAddress(); + if (bytes.length != 4) { + return true; + } else { + int firstOctets = bytes[0] & 0xff; + int firstInOctal = parseWithOctal(String.valueOf(firstOctets)); + int firstInHex = Integer.parseInt(String.valueOf(firstOctets), 16); + if (firstInOctal == 127 || firstInHex == 127) { + return bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 1; + } else if (firstInOctal == 10 || firstInHex == 10) { + return true; + } else if (firstInOctal == 172 || firstInHex == 172) { + int secondOctets = bytes[1] & 0xff; + int secondInOctal = parseWithOctal(String.valueOf(secondOctets)); + int secondInHex = Integer.parseInt(String.valueOf(secondOctets), 16); + return (secondInOctal >= 16 && secondInOctal <= 32) || (secondInHex >= 16 && secondInHex <= 32); + } else if (firstInOctal == 192 || firstInHex == 192) { + int secondOctets = bytes[1] & 0xff; + int secondInOctal = parseWithOctal(String.valueOf(secondOctets)); + int secondInHex = Integer.parseInt(String.valueOf(secondOctets), 16); + return secondInOctal == 168 || secondInHex == 168; + } + } + } + } return Arrays.stream(ipAddress).anyMatch(x -> x.isSiteLocalAddress() || x.isLoopbackAddress() || x.isAnyLocalAddress()); } + + private static int parseWithOctal(String input) { + try { + return Integer.parseInt(input, 8); + } catch (NumberFormatException e) { + return Integer.parseInt(input); + } + } } diff --git a/ml-algorithms/src/test/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactoryTests.java b/ml-algorithms/src/test/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactoryTests.java index 5a1b94c06c..4fbf9888fd 100644 --- a/ml-algorithms/src/test/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactoryTests.java +++ b/ml-algorithms/src/test/java/org/opensearch/ml/engine/httpclient/MLHttpClientFactoryTests.java @@ -11,9 +11,11 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.net.InetAddress; import java.net.UnknownHostException; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; public class MLHttpClientFactoryTests { @@ -43,6 +45,45 @@ public void test_validateIp_privateIp_throwException() throws UnknownHostExcepti MLHttpClientFactory.validateIp("localhost"); } + @Test + public void test_validateIp_rarePrivateIp_throwException() throws UnknownHostException { + try { + MLHttpClientFactory.validateIp("0254.020.00.01"); + } catch (IllegalArgumentException e) { + assertNotNull(e); + } + + try { + MLHttpClientFactory.validateIp("172.1048577"); + } catch (IllegalArgumentException e) { + assertNotNull(e); + } + + try { + MLHttpClientFactory.validateIp("2886729729"); + } catch (IllegalArgumentException e) { + assertNotNull(e); + } + + try { + MLHttpClientFactory.validateIp("192.11010049"); + } catch (IllegalArgumentException e) { + assertNotNull(e); + } + + try { + MLHttpClientFactory.validateIp("3232300545"); + } catch (IllegalArgumentException e) { + assertNotNull(e); + } + + try { + MLHttpClientFactory.validateIp("0:0:0:0:0:ffff:127.0.0.1"); + } catch (IllegalArgumentException e) { + assertNotNull(e); + } + } + @Test public void test_validateSchemaAndPort_success() { HttpHost httpHost = new HttpHost("api.openai.com", 8080, "https");