From 84a490e9d6e6e2de09d14167e5719b467fc24e59 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 9 Jun 2026 12:07:00 +0700 Subject: [PATCH 1/2] IGNITE-28743: Block remote HTTP/HTTPS/FTP URLs in resolveSpringUrl to prevent RCE via JDBC cfg:// --- .../ignite/internal/util/IgniteUtils.java | 40 +++++++++ .../internal/util/IgniteUtilsSelfTest.java | 89 +++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index 1513346138589..e0d40d85503aa 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -387,6 +387,21 @@ public abstract class IgniteUtils extends CommonUtils { /** Ignite Work Directory. */ public static final String IGNITE_WORK_DIR = System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR); + /** + * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. + * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. + * FTP is always blocked regardless of this property due to MITM risk. + */ + public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; + + /** URL schemes that load remote content and are blocked by default in Spring configuration. */ + private static final Set REMOTE_CFG_SCHEMES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("http", "https", "ftp", "ftps"))); + + /** URL schemes that are always blocked regardless of system property due to security risk. */ + private static final Set ALWAYS_BLOCKED_CFG_SCHEMES = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList("ftp", "ftps"))); + /** Random is used to get random server node to authentication from client node. */ private static final Random RND = new Random(System.currentTimeMillis()); @@ -2600,6 +2615,31 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc try { url = new URL(springCfgPath); + + String scheme = url.getProtocol().toLowerCase(); + + if (REMOTE_CFG_SCHEMES.contains(scheme)) { + // FTP is always blocked — unencrypted, susceptible to MITM + if (ALWAYS_BLOCKED_CFG_SCHEMES.contains(scheme)) + throw new IgniteCheckedException( + "Spring configuration URLs with scheme '" + scheme + "' are always blocked " + + "due to security risk (unencrypted transfer, MITM vulnerability). " + + "Use HTTPS or a local file/classpath reference instead. " + + "Provided host: " + url.getHost() + ); + + // HTTP/HTTPS blocked by default, allowed via system property + boolean allowRemote = Boolean.getBoolean(IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); + + if (!allowRemote) + throw new IgniteCheckedException( + "Remote Spring configuration URLs (http/https) are not allowed by default " + + "to prevent remote code execution via attacker-controlled Spring XML. " + + "Provided host: " + url.getHost() + ". " + + "To allow remote URLs set system property: -D" + + IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" + ); + } } catch (MalformedURLException e) { url = resolveIgniteUrl(springCfgPath); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java index 19f6ad17c38d8..6ed838c68ec67 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java @@ -1615,6 +1615,95 @@ public void testLongToBytes() { } } + /** + * Test that remote HTTP URL in Spring cfg is blocked by default. + */ + @Test + public void testResolveSpringUrlBlocksHttpByDefault() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "Remote Spring configuration URLs"); + } + + /** + * Test that remote HTTPS URL in Spring cfg is blocked by default. + */ + @Test + public void testResolveSpringUrlBlocksHttpsByDefault() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("https://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "Remote Spring configuration URLs"); + } + + /** + * Test that remote FTP URL in Spring cfg is blocked by default. + */ + @Test + public void testResolveSpringUrlBlocksFtpByDefault() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("ftp://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "always blocked"); + } + + /** + * Test that error message contains guidance on how to enable remote URLs. + */ + @Test + public void testResolveSpringUrlErrorMessageContainsGuidance() { + try { + IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); + fail("Expected IgniteCheckedException"); + } + catch (IgniteCheckedException e) { + assertTrue( + "Error message should contain system property name", + e.getMessage().contains(IgniteUtils.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL) + ); + assertFalse( + "Error message should not contain full URL to avoid credential leak", + e.getMessage().contains("http://attacker.example.com/evil.xml") + ); + assertTrue( + "Error message should contain host", + e.getMessage().contains("attacker.example.com") + ); + } + } + + /** + * Test that remote HTTP URL is allowed when system property is set. + */ + @Test + @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + public void testResolveSpringUrlAllowsHttpWhenPropertySet() { + // Should not throw — validation passes when flag is true. + // Will throw MalformedURLException or connection error, not our security check. + try { + IgniteUtils.resolveSpringUrl("http://127.0.0.1:1/nonexistent.xml"); + } + catch (IgniteCheckedException e) { + assertFalse( + "Should not throw security exception when flag is enabled", + e.getMessage().contains("Remote Spring configuration URLs") + ); + } + } + + /** + * Test that FTP is always blocked even when remote URL property is set. + */ + @Test + @WithSystemProperty(key = "ignite.spring.cfg.allowRemoteUrl", value = "true") + public void testResolveSpringUrlFtpAlwaysBlocked() { + assertThrows(log, () -> { + IgniteUtils.resolveSpringUrl("ftp://attacker.example.com/evil.xml"); + return null; + }, IgniteCheckedException.class, "always blocked"); + } + /** */ private byte[] asByteArray(String text) { String[] split = text.split("-"); From 27d5cc3c96a5c1f5796f843b7cf169a3a6311607 Mon Sep 17 00:00:00 2001 From: Kirill Anisimov Date: Tue, 9 Jun 2026 12:37:44 +0700 Subject: [PATCH 2/2] IGNITE-28743 Validate URL scheme in resolveSpringUrl --- .../apache/ignite/IgniteSystemProperties.java | 9 +++++++++ .../ignite/internal/util/IgniteUtils.java | 11 ++--------- .../internal/util/IgniteUtilsSelfTest.java | 18 ++++-------------- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java index b1c36a9015827..8c52a124ede20 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java @@ -1967,6 +1967,15 @@ public final class IgniteSystemProperties extends IgniteCommonsSystemProperties @SystemProperty(value = "Packages list to expose in configuration view") public static final String IGNITE_CONFIGURATION_VIEW_PACKAGES = "IGNITE_CONFIGURATION_VIEW_PACKAGES"; + + /** + * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. + * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. + * FTP is always blocked regardless of this property due to MITM risk. + */ + @SystemProperty(value = "Allow remote HTTP/HTTPS URLs when loading Spring XML configuration") + public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; + /** * Enforces singleton. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java index e0d40d85503aa..fd18f13a161e0 100755 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java @@ -387,13 +387,6 @@ public abstract class IgniteUtils extends CommonUtils { /** Ignite Work Directory. */ public static final String IGNITE_WORK_DIR = System.getenv(IgniteSystemProperties.IGNITE_WORK_DIR); - /** - * System property to allow remote HTTP/HTTPS URLs when loading Spring XML configuration. - * Remote URLs are blocked by default to prevent RCE via attacker-controlled Spring XML. - * FTP is always blocked regardless of this property due to MITM risk. - */ - public static final String IGNITE_ALLOW_REMOTE_SPRING_CFG_URL = "ignite.spring.cfg.allowRemoteUrl"; - /** URL schemes that load remote content and are blocked by default in Spring configuration. */ private static final Set REMOTE_CFG_SCHEMES = Collections.unmodifiableSet( new HashSet<>(Arrays.asList("http", "https", "ftp", "ftps"))); @@ -2629,7 +2622,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc ); // HTTP/HTTPS blocked by default, allowed via system property - boolean allowRemote = Boolean.getBoolean(IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); + boolean allowRemote = Boolean.getBoolean(IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL); if (!allowRemote) throw new IgniteCheckedException( @@ -2637,7 +2630,7 @@ public static URL resolveSpringUrl(String springCfgPath) throws IgniteCheckedExc "to prevent remote code execution via attacker-controlled Spring XML. " + "Provided host: " + url.getHost() + ". " + "To allow remote URLs set system property: -D" + - IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" + IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL + "=true" ); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java index 6ed838c68ec67..e0149ef69bd41 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java @@ -1615,17 +1615,6 @@ public void testLongToBytes() { } } - /** - * Test that remote HTTP URL in Spring cfg is blocked by default. - */ - @Test - public void testResolveSpringUrlBlocksHttpByDefault() { - assertThrows(log, () -> { - IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); - return null; - }, IgniteCheckedException.class, "Remote Spring configuration URLs"); - } - /** * Test that remote HTTPS URL in Spring cfg is blocked by default. */ @@ -1649,10 +1638,11 @@ public void testResolveSpringUrlBlocksFtpByDefault() { } /** - * Test that error message contains guidance on how to enable remote URLs. + * Test that remote HTTP URL in Spring cfg is blocked by default + * and error message contains guidance on how to enable remote URLs. */ @Test - public void testResolveSpringUrlErrorMessageContainsGuidance() { + public void testResolveSpringUrlBlocksHttpByDefault() { try { IgniteUtils.resolveSpringUrl("http://attacker.example.com/evil.xml"); fail("Expected IgniteCheckedException"); @@ -1660,7 +1650,7 @@ public void testResolveSpringUrlErrorMessageContainsGuidance() { catch (IgniteCheckedException e) { assertTrue( "Error message should contain system property name", - e.getMessage().contains(IgniteUtils.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL) + e.getMessage().contains(IgniteSystemProperties.IGNITE_ALLOW_REMOTE_SPRING_CFG_URL) ); assertFalse( "Error message should not contain full URL to avoid credential leak",