CVE-2024-36404: Insufficient Information (NVD-CWE-noinfo)
value: GeoTools is an open source Java library that provides tools for geospatial data. Prior to versions 31.2, 30.4, and 29.6, Remote Code Execution (RCE) is possible if an application uses certain GeoTools functionality to evaluate XPath expressions supplied by user input. Versions 31.2, 30.4, and 29.6 contain a fix for this issue. As a workaround, GeoTools can operate with reduced functionality by removing the `gt-complex` jar from one's application. As an example of the impact, application schema `datastore` would not function without the ability to use XPath expressions to query complex content. Alternatively, one may utilize a drop-in replacement GeoTools jar from SourceForge for versions 31.1, 30.3, 30.2, 29.2, 28.2, 27.5, 27.4, 26.7, 26.4, 25.2, and 24.0. These jars are for download only and are not available from maven central, intended to quickly provide a fix to affected applications.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-07-02T14:15Z
Repository: https://github.com/geotools/geotools
Language: Java
Files changed: modules/extension/app-schema/app-schema/src/main/java/org/geotools/appschema/util/XmlXpathUtilites.java, modules/extension/app-schema/app-schema/src/test/java/org/geotools/appschema/util/XmlXpathUtilitesTest.java, modules/extension/complex/src/main/java/org/geotools/data/complex/expression/FeaturePropertyAccessorFactory.java, modules/extension/complex/src/main/java/org/geotools/data/complex/expression/MapPropertyAccessorFactory.java, modules/extension/complex/src/test/java/org/geotools/filter/expression/FeaturePropertyAccessorTest.java, modules/extension/complex/src/test/java/org/geotools/filter/expression/MapPropertyAccessorTest.java, modules/extension/xsd/xsd-core/pom.xml, modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathStreamingParserHandler.java, modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathUtils.java, modules/extension/xsd/xsd-core/src/test/java/org/geotools/xsd/StreamingParserTest.java
Commit date: 2024-06-05 09:31:19 -0700
Commit message: [GEOT-7587] Improve handling of XPath expressions

Diff: diff --git a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/appschema/util/XmlXpathUtilites.java b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/appschema/util/XmlXpathUtilites.java
index 74247a2282..344e33ac7b 100644
--- a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/appschema/util/XmlXpathUtilites.java
+++ b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/appschema/util/XmlXpathUtilites.java
@@ -16,14 +16,14 @@
  */
 
 package org.geotools.appschema.util;
 
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.List;
 import java.util.Scanner;
 import org.apache.commons.jxpath.JXPathContext;
+import org.geotools.xsd.impl.jxpath.JXPathUtils;
 import org.jdom2.Document;
 import org.xml.sax.helpers.NamespaceSupport;
 
 /**
  * Ulities class for xpath handling on a jdom document object
@@ -39,11 +39,11 @@ public class XmlXpathUtilites {
      * @param doc xml to search
      * @return a list of values matching the xpath in the xml supplied
      */
     public static List<String> getXPathValues(
             NamespaceSupport ns, String xpathString, Document doc) {
-        JXPathContext context = initialiseContext(ns, doc);
+        JXPathContext context = JXPathUtils.newSafeContext(doc, true, ns, false);
         return getXPathValues(xpathString, context);
     }
 
     /**
      * @param ns namespaces
@@ -67,37 +67,19 @@ public static int countXPathNodes(NamespaceSupport ns, String xpathString, Docum
      * @return the (single) value matching the xpath in the xml supplied
      */
     public static String getSingleXPathValue(
             NamespaceSupport ns, String xpathString, Document doc) {
         String id = null;
-        JXPathContext context = initialiseContext(ns, doc);
         try {
-            Object ob = context.getValue(xpathString);
+            Object ob = JXPathUtils.newSafeContext(doc, true, ns, false).getValue(xpathString);
             id = (String) ob;
         } catch (RuntimeException e) {
             throw new RuntimeException("Error reading xpath " + xpathString, e);
         }
         return id;
     }
 
-    private static JXPathContext initialiseContext(NamespaceSupport ns, Document doc) {
-        JXPathContext context = JXPathContext.newContext(doc);
-        addNamespaces(ns, context);
-        context.setLenient(true);
-        return context;
-    }
-
-    private static void addNamespaces(NamespaceSupport ns, JXPathContext context) {
-        @SuppressWarnings("unchecked")
-        Enumeration<String> prefixes = ns.getPrefixes();
-        while (prefixes.hasMoreElements()) {
-            String prefix = prefixes.nextElement();
-            String uri = ns.getURI(prefix);
-            context.registerNamespace(prefix, uri);
-        }
-    }
-
     /**
      * Remove indexes from an xpath string.
      *
      * @param xpath xpath string
      * @return unindexed xpath string
diff --git a/modules/extension/app-schema/app-schema/src/test/java/org/geotools/appschema/util/XmlXpathUtilitesTest.java b/modules/extension/app-schema/app-schema/src/test/java/org/geotools/appschema/util/XmlXpathUtilitesTest.java
new file mode 100644
index 0000000000..919d9af0c1
--- /dev/null
+++ b/modules/extension/app-schema/app-schema/src/test/java/org/geotools/appschema/util/XmlXpathUtilitesTest.java
@@ -0,0 +1,58 @@
+/*
+ *    GeoTools - The Open Source Java GIS Toolkit
+ *    http://geotools.org
+ *
+ *    (C) 2024, Open Source Geospatial Foundation (OSGeo)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.appschema.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+public class XmlXpathUtilitesTest {
+
+    @Test
+    public void testGetXPathValuesWithJavaMethod() {
+        RuntimeException exception =
+                assertThrows(
+                        RuntimeException.class,
+                        () ->
+                                XmlXpathUtilites.getXPathValues(
+                                        null, "java.lang.Thread.sleep(30000)", null));
+        assertEquals("Error reading xpath java.lang.Thread.sleep(30000)", exception.getMessage());
+    }
+
+    @Test
+    public void testCountXPathNodesWithJavaMethod() {
+        RuntimeException exception =
+                assertThrows(
+                        RuntimeException.class,
+                        () ->
+                                XmlXpathUtilites.countXPathNodes(
+                                        null, "java.lang.Thread.sleep(30000)", null));
+        assertEquals("Error reading xpath java.lang.Thread.sleep(30000)", exception.getMessage());
+    }
+
+    @Test
+    public void testGetSingleXPathValueWithJavaMethod() {
+        RuntimeException exception =
+                assertThrows(
+                        RuntimeException.class,
+                        () ->
+                                XmlXpathUtilites.getSingleXPathValue(
+                                        null, "java.lang.Thread.sleep(30000)", null));
+        assertEquals("Error reading xpath java.lang.Thread.sleep(30000)", exception.getMessage());
+    }
+}
diff --git a/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/FeaturePropertyAccessorFactory.java b/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/FeaturePropertyAccessorFactory.java
index f29035a632..f519cee685 100644
--- a/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/FeaturePropertyAccessorFactory.java
+++ b/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/FeaturePropertyAccessorFactory.java
@@ -18,11 +18,10 @@
 package org.geotools.data.complex.expression;
 
 import static org.geotools.filter.expression.SimpleFeaturePropertyAccessorFactory.DEFAULT_GEOMETRY_NAME;
 
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
 import java.util.regex.Pattern;
 import org.apache.commons.jxpath.JXPathContext;
 import org.apache.commons.jxpath.Pointer;
@@ -31,10 +30,11 @@
 import org.geotools.api.feature.ComplexAttribute;
 import org.geotools.api.feature.Feature;
 import org.geotools.api.feature.GeometryAttribute;
 import org.geotools.api.feature.IllegalAttributeException;
 import org.geotools.api.feature.simple.SimpleFeature;
+import org.geotools.api.feature.simple.SimpleFeatureType;
 import org.geotools.api.feature.type.AttributeDescriptor;
 import org.geotools.api.feature.type.AttributeType;
 import org.geotools.api.feature.type.ComplexType;
 import org.geotools.api.feature.type.FeatureType;
 import org.geotools.api.feature.type.GeometryDescriptor;
@@ -42,10 +42,11 @@
 import org.geotools.data.complex.feature.xpath.AttributeNodePointer;
 import org.geotools.data.complex.feature.xpath.AttributeNodePointerFactory;
 import org.geotools.filter.expression.PropertyAccessor;
 import org.geotools.filter.expression.PropertyAccessorFactory;
 import org.geotools.util.factory.Hints;
+import org.geotools.xsd.impl.jxpath.JXPathUtils;
 import org.locationtech.jts.geom.Geometry;
 import org.xml.sax.helpers.NamespaceSupport;
 
 /**
  * Creates a namespace aware property accessor for ISO Features.
@@ -81,11 +82,12 @@ public class FeaturePropertyAccessorFactory implements PropertyAccessorFactory {
 
     @Override
     public PropertyAccessor createPropertyAccessor(
             Class type, String xpath, Class target, Hints hints) {
 
-        if (SimpleFeature.class.isAssignableFrom(type)) {
+        if (SimpleFeature.class.isAssignableFrom(type)
+                || SimpleFeatureType.class.isAssignableFrom(type)) {
             /*
              * This class is not intended for use with SimpleFeature and causes problems when
              * discovered via SPI and used by code expecting SimpleFeature behaviour. In particular
              * WMS styling code may fail when this class is present. See GEOS-3525.
              */
@@ -258,18 +260,12 @@ public boolean canHandle(Object object, String xpath, Class target) {
         @Override
         @SuppressWarnings("unchecked")
         public <T> T get(Object object, String xpath, Class<T> target)
                 throws IllegalArgumentException {
 
-            JXPathContext context = JXPathContext.newContext(object);
-            Enumeration declaredPrefixes = namespaces.getDeclaredPrefixes();
-            while (declaredPrefixes.hasMoreElements()) {
-                String prefix = (String) declaredPrefixes.nextElement();
-                String uri = namespaces.getURI(prefix);
-                context.registerNamespace(prefix, uri);
-            }
-
+            JXPathContext context =
+                    JXPathUtils.newSafeContext(object, false, this.namespaces, true);
             Iterator it = context.iteratePointers(xpath);
             List results = new ArrayList<>();
             while (it.hasNext()) {
                 Pointer pointer = (Pointer) it.next();
                 if (pointer instanceof AttributeNodePointer) {
@@ -294,17 +290,12 @@ public void set(Object object, String xpath, Object value, Class target)
 
             if (object instanceof FeatureType) {
                 throw new IllegalAttributeException(null, "feature type is immutable");
             }
 
-            JXPathContext context = JXPathContext.newContext(object);
-            Enumeration declaredPrefixes = namespaces.getDeclaredPrefixes();
-            while (declaredPrefixes.hasMoreElements()) {
-                String prefix = (String) declaredPrefixes.nextElement();
-                String uri = namespaces.getURI(prefix);
-                context.registerNamespace(prefix, uri);
-            }
+            JXPathContext context =
+                    JXPathUtils.newSafeContext(object, false, this.namespaces, true);
             context.setValue(xpath, value);
 
             assert value == context.getValue(xpath);
         }
     }
diff --git a/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/MapPropertyAccessorFactory.java b/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/MapPropertyAccessorFactory.java
index 9a14e6f88e..e0443126c7 100644
--- a/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/MapPropertyAccessorFactory.java
+++ b/modules/extension/complex/src/main/java/org/geotools/data/complex/expression/MapPropertyAccessorFactory.java
@@ -16,15 +16,15 @@
  */
 
 package org.geotools.data.complex.expression;
 
 import java.util.Map;
-import org.apache.commons.jxpath.JXPathContext;
 import org.geotools.api.feature.IllegalAttributeException;
 import org.geotools.filter.expression.PropertyAccessor;
 import org.geotools.filter.expression.PropertyAccessorFactory;
 import org.geotools.util.factory.Hints;
+import org.geotools.xsd.impl.jxpath.JXPathUtils;
 
 /**
  * A {@link PropertyAccessorFactory} that returns a {@link PropertyAccessor} capable of evaluating
  * single attribute names from a {@link Map}.
  *
@@ -61,13 +61,11 @@ public boolean canHandle(Object object, String xpath, Class target) {
 
                 @Override
                 @SuppressWarnings("unchecked")
                 public <T> T get(Object object, String xpath, Class<T> target)
                         throws IllegalArgumentException {
-                    JXPathContext context = JXPathContext.newContext(object);
-                    context.setLenient(true);
-                    return (T) context.getValue(xpath);
+                    return (T) JXPathUtils.newSafeContext(object, true).getValue(xpath);
                 }
 
                 @Override
                 public void set(Object object, String xpath, Object value, Class target)
                         throws IllegalAttributeException, IllegalArgumentException {
diff --git a/modules/extension/complex/src/test/java/org/geotools/filter/expression/FeaturePropertyAccessorTest.java b/modules/extension/complex/src/test/java/org/geotools/filter/expression/FeaturePropertyAccessorTest.java
index e30239d4c0..a3af5be7df 100644
--- a/modules/extension/complex/src/test/java/org/geotools/filter/expression/FeaturePropertyAccessorTest.java
+++ b/modules/extension/complex/src/test/java/org/geotools/filter/expression/FeaturePropertyAccessorTest.java
@@ -288,10 +288,24 @@ public void testComplexFeature() {
         ex =
                 new AttributeExpressionImpl(
                         "eg:complexAttribute/eg:rootAttribute/eg:multiLeafAttribute",
                         new Hints(FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, NAMESPACES));
         assertEquals(multiLeafDesc, ex.evaluate(fType));
+
+        // test that executing Java methods is blocked
+        ex =
+                new AttributeExpressionImpl(
+                        "java.lang.Thread.sleep(30000)",
+                        new Hints(FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, NAMESPACES));
+        long start = System.currentTimeMillis();
+        assertNull(ex.evaluate(feature));
+        long runtime = System.currentTimeMillis() - start;
+        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
+        start = System.currentTimeMillis();
+        assertNull(ex.evaluate(fType));
+        runtime = System.currentTimeMillis() - start;
+        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
     }
 
     public static FeatureType createFeatureType() {
         FeatureTypeFactory tfac = new UniqueNameFeatureTypeFactoryImpl();
         Name fName = new NameImpl(EG, "complexFeatureType");
diff --git a/modules/extension/complex/src/test/java/org/geotools/filter/expression/MapPropertyAccessorTest.java b/modules/extension/complex/src/test/java/org/geotools/filter/expression/MapPropertyAccessorTest.java
new file mode 100644
index 0000000000..8b5d5e5b03
--- /dev/null
+++ b/modules/extension/complex/src/test/java/org/geotools/filter/expression/MapPropertyAccessorTest.java
@@ -0,0 +1,37 @@
+/*
+ *    GeoTools - The Open Source Java GIS Toolkit
+ *    http://geotools.org
+ *
+ *    (C) 2024, Open Source Geospatial Foundation (OSGeo)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.filter.expression;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import org.geotools.api.filter.expression.PropertyName;
+import org.geotools.factory.CommonFactoryFinder;
+import org.junit.Test;
+
+public class MapPropertyAccessorTest {
+
+    @Test
+    public void testAccessWithJavaMethod() {
+        PropertyName property =
+                CommonFactoryFinder.getFilterFactory().property("java.lang.Thread.sleep(30000)");
+        long start = System.currentTimeMillis();
+        property.evaluate(Collections.emptyMap());
+        long runtime = System.currentTimeMillis() - start;
+        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
+    }
+}
diff --git a/modules/extension/xsd/xsd-core/pom.xml b/modules/extension/xsd/xsd-core/pom.xml
index aa33902ea7..9b394793ca 100644
--- a/modules/extension/xsd/xsd-core/pom.xml
+++ b/modules/extension/xsd/xsd-core/pom.xml
@@ -88,24 +88,10 @@
     </dependency>
     <dependency>
       <groupId>commons-jxpath</groupId>
       <artifactId>commons-jxpath</artifactId>
       <version>1.3</version>
-      <exclusions>
-        <exclusion>
-          <groupId>xerces</groupId>
-          <artifactId>xerces</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>servletapi</groupId>
-          <artifactId>servletapi</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>junit</groupId>
-          <artifactId>junit</artifactId>
-        </exclusion>
-      </exclusions>
     </dependency>
     <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-collections4</artifactId>
     </dependency>
diff --git a/modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathStreamingParserHandler.java b/modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathStreamingParserHandler.java
index 98a63ede44..1611284780 100644
--- a/modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathStreamingParserHandler.java
+++ b/modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathStreamingParserHandler.java
@@ -15,12 +15,10 @@
  *    Lesser General Public License for more details.
  */
 package org.geotools.xsd.impl.jxpath;
 
 import java.util.Iterator;
-import org.apache.commons.jxpath.JXPathContext;
-import org.apache.commons.jxpath.JXPathContextFactory;
 import org.apache.commons.jxpath.JXPathIntrospector;
 import org.geotools.xsd.Configuration;
 import org.geotools.xsd.Node;
 import org.geotools.xsd.impl.ElementHandler;
 import org.geotools.xsd.impl.NodeImpl;
@@ -45,15 +43,11 @@ protected boolean stream(ElementHandler handler) {
         JXPathIntrospector.registerDynamicClass(NodeImpl.class, NodePropertyHandler.class);
 
         //        ElementHandler rootHandler =
         //        	((DocumentHandler) handlers.firstElement()).getDocumentElementHandler();
         Node root = handlers.firstElement().getParseNode();
-        JXPathContext jxpContext = JXPathContextFactory.newInstance().newContext(null, root);
-
-        jxpContext.setLenient(true);
-
-        Iterator itr = jxpContext.iterate(xpath);
+        Iterator itr = JXPathUtils.newSafeContext(root, true).iterate(xpath);
 
         while (itr.hasNext()) {
             Object obj = itr.next();
 
             if (handler.getParseNode().equals(obj)) {
diff --git a/modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathUtils.java b/modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathUtils.java
new file mode 100644
index 0000000000..fe543f5eff
--- /dev/null
+++ b/modules/extension/xsd/xsd-core/src/main/java/org/geotools/xsd/impl/jxpath/JXPathUtils.java
@@ -0,0 +1,64 @@
+/*
+ *    GeoTools - The Open Source Java GIS Toolkit
+ *    http://geotools.org
+ *
+ *    (C) 2024, Open Source Geospatial Foundation (OSGeo)
+ *
+ *    This library is free software; you can redistribute it and/or
+ *    modify it under the terms of the GNU Lesser General Public
+ *    License as published by the Free Software Foundation;
+ *    version 2.1 of the License.
+ *
+ *    This library is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *    Lesser General Public License for more details.
+ */
+package org.geotools.xsd.impl.jxpath;
+
+import java.util.Enumeration;
+import org.apache.commons.jxpath.FunctionLibrary;
+import org.apache.commons.jxpath.JXPathContext;
+import org.xml.sax.helpers.NamespaceSupport;
+
+/** Contains utility methods to create safe {@code JXPathContext} objects. */
+public class JXPathUtils {
+
+    /**
+     * Creates a {@code JXPathContext} that disables calling Java methods from XPath expressions.
+     *
+     * @param contextBean the root node object
+     * @param lenient whether the context is in lenient mode
+     * @return the context
+     */
+    public static JXPathContext newSafeContext(Object contextBean, boolean lenient) {
+        return newSafeContext(contextBean, lenient, null, true);
+    }
+
+    /**
+     * Creates a {@code JXPathContext} that disables calling Java methods from XPath expressions.
+     *
+     * @param contextBean the root node object
+     * @param lenient whether the context is in lenient mode
+     * @param ns the namespaces
+     * @param declared true to include all declared prefixes; false for all active prefixes
+     * @return the context
+     */
+    @SuppressWarnings("unchecked")
+    public static JXPathContext newSafeContext(
+            Object contextBean, boolean lenient, NamespaceSupport ns, boolean declared) {
+        JXPathContext context = JXPathContext.newContext(contextBean);
+        context.setLenient(lenient);
+        if (ns != null) {
+            Enumeration<String> prefixes = declared ? ns.getDeclaredPrefixes() : ns.getPrefixes();
+            while (prefixes.hasMoreElements()) {
+                String prefix = prefixes.nextElement();
+                String uri = ns.getURI(prefix);
+                context.registerNamespace(prefix, uri);
+            }
+        }
+        // Set empty function library to prevent calling functions
+        context.setFunctions(new FunctionLibrary());
+        return context;
+    }
+}
diff --git a/modules/extension/xsd/xsd-core/src/test/java/org/geotools/xsd/StreamingParserTest.java b/modules/extension/xsd/xsd-core/src/test/java/org/geotools/xsd/StreamingParserTest.java
index e9ca1f270e..d0e8349186 100644
--- a/modules/extension/xsd/xsd-core/src/test/java/org/geotools/xsd/StreamingParserTest.java
+++ b/modules/extension/xsd/xsd-core/src/test/java/org/geotools/xsd/StreamingParserTest.java
@@ -15,10 +15,11 @@
  *    Lesser General Public License for more details.
  */
 package org.geotools.xsd;
 
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayInputStream;
 import javax.xml.namespace.QName;
 import org.geotools.ml.MLConfiguration;
 import org.geotools.ml.bindings.ML;
@@ -37,6 +38,18 @@ public void testParseXXE() throws Exception {
                 new StreamingParser(new MLConfiguration(), in, new QName(ML.NAMESPACE, "mail"));
         parser.setEntityResolver(PreventLocalEntityResolver.INSTANCE);
         // StreamingParser returns null if the parsing fails
         assertNull(parser.parse());
     }
+
+    @Test
+    public void testParseWithJavaMethod() throws Exception {
+        ByteArrayInputStream in = new ByteArrayInputStream("<mails></mails>".getBytes());
+        StreamingParser parser =
+                new StreamingParser(new MLConfiguration(), in, "java.lang.Thread.sleep(30000)");
+        // StreamingParser returns null if the parsing fails
+        long start = System.currentTimeMillis();
+        assertNull(parser.parse());
+        long runtime = System.currentTimeMillis() - start;
+        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
+    }
 }

Vulnerable code: import java.util.Enumeration;
        JXPathContext context = initialiseContext(ns, doc);
        JXPathContext context = initialiseContext(ns, doc);
            Object ob = context.getValue(xpathString);
    private static JXPathContext initialiseContext(NamespaceSupport ns, Document doc) {
        JXPathContext context = JXPathContext.newContext(doc);
        addNamespaces(ns, context);
        context.setLenient(true);
        return context;
    }

    private static void addNamespaces(NamespaceSupport ns, JXPathContext context) {
        @SuppressWarnings("unchecked")
        Enumeration<String> prefixes = ns.getPrefixes();
        while (prefixes.hasMoreElements()) {
            String prefix = prefixes.nextElement();
            String uri = ns.getURI(prefix);
            context.registerNamespace(prefix, uri);
        }
    }

import java.util.Enumeration;
        if (SimpleFeature.class.isAssignableFrom(type)) {
            JXPathContext context = JXPathContext.newContext(object);
            Enumeration declaredPrefixes = namespaces.getDeclaredPrefixes();
            while (declaredPrefixes.hasMoreElements()) {
                String prefix = (String) declaredPrefixes.nextElement();
                String uri = namespaces.getURI(prefix);
                context.registerNamespace(prefix, uri);
            }

            JXPathContext context = JXPathContext.newContext(object);
            Enumeration declaredPrefixes = namespaces.getDeclaredPrefixes();
            while (declaredPrefixes.hasMoreElements()) {
                String prefix = (String) declaredPrefixes.nextElement();
                String uri = namespaces.getURI(prefix);
                context.registerNamespace(prefix, uri);
            }
import org.apache.commons.jxpath.JXPathContext;
                    JXPathContext context = JXPathContext.newContext(object);
                    context.setLenient(true);
                    return (T) context.getValue(xpath);
      <exclusions>
        <exclusion>
          <groupId>xerces</groupId>
          <artifactId>xerces</artifactId>
        </exclusion>
        <exclusion>
          <groupId>servletapi</groupId>
          <artifactId>servletapi</artifactId>
        </exclusion>
        <exclusion>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
        </exclusion>
      </exclusions>
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathContextFactory;
        JXPathContext jxpContext = JXPathContextFactory.newInstance().newContext(null, root);

        jxpContext.setLenient(true);

        Iterator itr = jxpContext.iterate(xpath);
Fixed code: import org.geotools.xsd.impl.jxpath.JXPathUtils;
        JXPathContext context = JXPathUtils.newSafeContext(doc, true, ns, false);
            Object ob = JXPathUtils.newSafeContext(doc, true, ns, false).getValue(xpathString);
/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2024, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.appschema.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

import org.junit.Test;

public class XmlXpathUtilitesTest {

    @Test
    public void testGetXPathValuesWithJavaMethod() {
        RuntimeException exception =
                assertThrows(
                        RuntimeException.class,
                        () ->
                                XmlXpathUtilites.getXPathValues(
                                        null, "java.lang.Thread.sleep(30000)", null));
        assertEquals("Error reading xpath java.lang.Thread.sleep(30000)", exception.getMessage());
    }

    @Test
    public void testCountXPathNodesWithJavaMethod() {
        RuntimeException exception =
                assertThrows(
                        RuntimeException.class,
                        () ->
                                XmlXpathUtilites.countXPathNodes(
                                        null, "java.lang.Thread.sleep(30000)", null));
        assertEquals("Error reading xpath java.lang.Thread.sleep(30000)", exception.getMessage());
    }

    @Test
    public void testGetSingleXPathValueWithJavaMethod() {
        RuntimeException exception =
                assertThrows(
                        RuntimeException.class,
                        () ->
                                XmlXpathUtilites.getSingleXPathValue(
                                        null, "java.lang.Thread.sleep(30000)", null));
        assertEquals("Error reading xpath java.lang.Thread.sleep(30000)", exception.getMessage());
    }
}
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.xsd.impl.jxpath.JXPathUtils;
        if (SimpleFeature.class.isAssignableFrom(type)
                || SimpleFeatureType.class.isAssignableFrom(type)) {
            JXPathContext context =
                    JXPathUtils.newSafeContext(object, false, this.namespaces, true);
            JXPathContext context =
                    JXPathUtils.newSafeContext(object, false, this.namespaces, true);
import org.geotools.xsd.impl.jxpath.JXPathUtils;
                    return (T) JXPathUtils.newSafeContext(object, true).getValue(xpath);

        // test that executing Java methods is blocked
        ex =
                new AttributeExpressionImpl(
                        "java.lang.Thread.sleep(30000)",
                        new Hints(FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, NAMESPACES));
        long start = System.currentTimeMillis();
        assertNull(ex.evaluate(feature));
        long runtime = System.currentTimeMillis() - start;
        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
        start = System.currentTimeMillis();
        assertNull(ex.evaluate(fType));
        runtime = System.currentTimeMillis() - start;
        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2024, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.filter.expression;

import static org.junit.Assert.assertTrue;

import java.util.Collections;
import org.geotools.api.filter.expression.PropertyName;
import org.geotools.factory.CommonFactoryFinder;
import org.junit.Test;

public class MapPropertyAccessorTest {

    @Test
    public void testAccessWithJavaMethod() {
        PropertyName property =
                CommonFactoryFinder.getFilterFactory().property("java.lang.Thread.sleep(30000)");
        long start = System.currentTimeMillis();
        property.evaluate(Collections.emptyMap());
        long runtime = System.currentTimeMillis() - start;
        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
    }
}
        Iterator itr = JXPathUtils.newSafeContext(root, true).iterate(xpath);
/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2024, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.xsd.impl.jxpath;

import java.util.Enumeration;
import org.apache.commons.jxpath.FunctionLibrary;
import org.apache.commons.jxpath.JXPathContext;
import org.xml.sax.helpers.NamespaceSupport;

/** Contains utility methods to create safe {@code JXPathContext} objects. */
public class JXPathUtils {

    /**
     * Creates a {@code JXPathContext} that disables calling Java methods from XPath expressions.
     *
     * @param contextBean the root node object
     * @param lenient whether the context is in lenient mode
     * @return the context
     */
    public static JXPathContext newSafeContext(Object contextBean, boolean lenient) {
        return newSafeContext(contextBean, lenient, null, true);
    }

    /**
     * Creates a {@code JXPathContext} that disables calling Java methods from XPath expressions.
     *
     * @param contextBean the root node object
     * @param lenient whether the context is in lenient mode
     * @param ns the namespaces
     * @param declared true to include all declared prefixes; false for all active prefixes
     * @return the context
     */
    @SuppressWarnings("unchecked")
    public static JXPathContext newSafeContext(
            Object contextBean, boolean lenient, NamespaceSupport ns, boolean declared) {
        JXPathContext context = JXPathContext.newContext(contextBean);
        context.setLenient(lenient);
        if (ns != null) {
            Enumeration<String> prefixes = declared ? ns.getDeclaredPrefixes() : ns.getPrefixes();
            while (prefixes.hasMoreElements()) {
                String prefix = prefixes.nextElement();
                String uri = ns.getURI(prefix);
                context.registerNamespace(prefix, uri);
            }
        }
        // Set empty function library to prevent calling functions
        context.setFunctions(new FunctionLibrary());
        return context;
    }
}
import static org.junit.Assert.assertTrue;

    @Test
    public void testParseWithJavaMethod() throws Exception {
        ByteArrayInputStream in = new ByteArrayInputStream("<mails></mails>".getBytes());
        StreamingParser parser =
                new StreamingParser(new MLConfiguration(), in, "java.lang.Thread.sleep(30000)");
        // StreamingParser returns null if the parsing fails
        long start = System.currentTimeMillis();
        assertNull(parser.parse());
        long runtime = System.currentTimeMillis() - start;
        assertTrue("java.lang.Thread.sleep(30000) was executed", runtime < 30000);
    }