This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git


The following commit(s) were added to refs/heads/master by this push:
     new ba81f5a35 Restrict reflective class loading in webdav 
ExceptionConverter (#771)
ba81f5a35 is described below

commit ba81f5a35d16567e9dcd14263e6e2182f2337945
Author: Naveed Khan <[email protected]>
AuthorDate: Mon Jul 13 22:17:34 2026 +0000

    Restrict reflective class loading in webdav ExceptionConverter (#771)
    
    A malicious WebDAV server could name any classpath class in its error 
response and have the client load and construct it. Load without initializing 
and only build Exception subtypes.
---
 .../vfs2/provider/webdav/ExceptionConverter.java   | 19 +++---
 .../provider/webdav/ExceptionConverterTest.java    | 78 ++++++++++++++++++++++
 .../vfs2/provider/webdav4/ExceptionConverter.java  | 19 +++---
 .../provider/webdav4/ExceptionConverterTest.java   | 78 ++++++++++++++++++++++
 4 files changed, 178 insertions(+), 16 deletions(-)

diff --git 
a/commons-vfs2-jackrabbit1/src/main/java/org/apache/commons/vfs2/provider/webdav/ExceptionConverter.java
 
b/commons-vfs2-jackrabbit1/src/main/java/org/apache/commons/vfs2/provider/webdav/ExceptionConverter.java
index a6d15f5a5..0ea75b806 100644
--- 
a/commons-vfs2-jackrabbit1/src/main/java/org/apache/commons/vfs2/provider/webdav/ExceptionConverter.java
+++ 
b/commons-vfs2-jackrabbit1/src/main/java/org/apache/commons/vfs2/provider/webdav/ExceptionConverter.java
@@ -62,14 +62,17 @@ public final class ExceptionConverter {
                         msg = DomUtil.getChildText(exc, "message", null);
                     }
                     if (DomUtil.hasChildElement(exc, "class", null)) {
-                        final Class<?> cl = 
Class.forName(DomUtil.getChildText(exc, "class", null));
-                        final Constructor<?> excConstr = 
cl.getConstructor(String.class);
-                        final Object o = excConstr.newInstance(msg);
-                        if (o instanceof FileSystemException) {
-                            return (FileSystemException) o;
-                        }
-                        if (o instanceof Exception) {
-                            return new FileSystemException(msg, (Exception) o);
+                        final Class<?> cl = 
Class.forName(DomUtil.getChildText(exc, "class", null), false,
+                                ExceptionConverter.class.getClassLoader());
+                        if (Exception.class.isAssignableFrom(cl)) {
+                            final Constructor<?> excConstr = 
cl.getConstructor(String.class);
+                            final Object o = excConstr.newInstance(msg);
+                            if (o instanceof FileSystemException) {
+                                return (FileSystemException) o;
+                            }
+                            if (o instanceof Exception) {
+                                return new FileSystemException(msg, 
(Exception) o);
+                            }
                         }
                     }
                 }
diff --git 
a/commons-vfs2-jackrabbit1/src/test/java/org/apache/commons/vfs2/provider/webdav/ExceptionConverterTest.java
 
b/commons-vfs2-jackrabbit1/src/test/java/org/apache/commons/vfs2/provider/webdav/ExceptionConverterTest.java
new file mode 100644
index 000000000..8eb34a568
--- /dev/null
+++ 
b/commons-vfs2-jackrabbit1/src/test/java/org/apache/commons/vfs2/provider/webdav/ExceptionConverterTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.webdav;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.jackrabbit.webdav.DavException;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+
+/**
+ * Tests {@link ExceptionConverter}.
+ */
+public class ExceptionConverterTest {
+
+    /** Marker class named by the crafted server response; must never be 
instantiated. */
+    public static final class Marker {
+        static volatile boolean instantiated;
+
+        public Marker(final String message) {
+            instantiated = true;
+        }
+    }
+
+    /**
+     * Builds a {@link DavException} whose error condition carries an {@code 
<exception><class>} element, as parsed from a
+     * malicious WebDAV server error body.
+     */
+    private DavException davExceptionNaming(final String className) throws 
Exception {
+        final String xml = "<exception><class>" + className + 
"</class><message>boom</message></exception>";
+        final DocumentBuilderFactory dbf = 
DocumentBuilderFactory.newInstance();
+        dbf.setNamespaceAware(true);
+        final Document doc = dbf.newDocumentBuilder().parse(new 
InputSource(new StringReader(xml)));
+        final Element condition = doc.getDocumentElement();
+        return new DavException(500, "err", null, condition);
+    }
+
+    @Test
+    public void testGenerateDoesNotInstantiateServerNamedClass() throws 
Exception {
+        Marker.instantiated = false;
+        final DavException cause = davExceptionNaming(Marker.class.getName());
+        final FileSystemException result = ExceptionConverter.generate(cause);
+        assertNotNull(result);
+        assertFalse(Marker.instantiated, "server-controlled class name must 
not be instantiated");
+    }
+
+    @Test
+    public void testGenerateStillWrapsExceptionType() throws Exception {
+        final DavException cause = 
davExceptionNaming(IOException.class.getName());
+        final FileSystemException result = ExceptionConverter.generate(cause);
+        assertNotNull(result);
+        assertInstanceOf(IOException.class, result.getCause());
+    }
+}
diff --git 
a/commons-vfs2-jackrabbit2/src/main/java/org/apache/commons/vfs2/provider/webdav4/ExceptionConverter.java
 
b/commons-vfs2-jackrabbit2/src/main/java/org/apache/commons/vfs2/provider/webdav4/ExceptionConverter.java
index e4ec8ab72..68cbe7b3c 100644
--- 
a/commons-vfs2-jackrabbit2/src/main/java/org/apache/commons/vfs2/provider/webdav4/ExceptionConverter.java
+++ 
b/commons-vfs2-jackrabbit2/src/main/java/org/apache/commons/vfs2/provider/webdav4/ExceptionConverter.java
@@ -49,14 +49,17 @@ public final class ExceptionConverter {
                         msg = DomUtil.getChildText(exc, "message", null);
                     }
                     if (DomUtil.hasChildElement(exc, "class", null)) {
-                        final Class<?> cl = 
Class.forName(DomUtil.getChildText(exc, "class", null));
-                        final Constructor<?> excConstr = 
cl.getConstructor(String.class);
-                        final Object o = excConstr.newInstance(msg);
-                        if (o instanceof FileSystemException) {
-                            return (FileSystemException) o;
-                        }
-                        if (o instanceof Exception) {
-                            return new FileSystemException(msg, (Exception) o);
+                        final Class<?> cl = 
Class.forName(DomUtil.getChildText(exc, "class", null), false,
+                                ExceptionConverter.class.getClassLoader());
+                        if (Exception.class.isAssignableFrom(cl)) {
+                            final Constructor<?> excConstr = 
cl.getConstructor(String.class);
+                            final Object o = excConstr.newInstance(msg);
+                            if (o instanceof FileSystemException) {
+                                return (FileSystemException) o;
+                            }
+                            if (o instanceof Exception) {
+                                return new FileSystemException(msg, 
(Exception) o);
+                            }
                         }
                     }
                 }
diff --git 
a/commons-vfs2-jackrabbit2/src/test/java/org/apache/commons/vfs2/provider/webdav4/ExceptionConverterTest.java
 
b/commons-vfs2-jackrabbit2/src/test/java/org/apache/commons/vfs2/provider/webdav4/ExceptionConverterTest.java
new file mode 100644
index 000000000..c31915cbe
--- /dev/null
+++ 
b/commons-vfs2-jackrabbit2/src/test/java/org/apache/commons/vfs2/provider/webdav4/ExceptionConverterTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.webdav4;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.jackrabbit.webdav.DavException;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+
+/**
+ * Tests {@link ExceptionConverter}.
+ */
+public class ExceptionConverterTest {
+
+    /** Marker class named by the crafted server response; must never be 
instantiated. */
+    public static final class Marker {
+        static volatile boolean instantiated;
+
+        public Marker(final String message) {
+            instantiated = true;
+        }
+    }
+
+    /**
+     * Builds a {@link DavException} whose error condition carries an {@code 
<exception><class>} element, as parsed from a
+     * malicious WebDAV server error body.
+     */
+    private DavException davExceptionNaming(final String className) throws 
Exception {
+        final String xml = "<exception><class>" + className + 
"</class><message>boom</message></exception>";
+        final DocumentBuilderFactory dbf = 
DocumentBuilderFactory.newInstance();
+        dbf.setNamespaceAware(true);
+        final Document doc = dbf.newDocumentBuilder().parse(new 
InputSource(new StringReader(xml)));
+        final Element condition = doc.getDocumentElement();
+        return new DavException(500, "err", null, condition);
+    }
+
+    @Test
+    public void testGenerateDoesNotInstantiateServerNamedClass() throws 
Exception {
+        Marker.instantiated = false;
+        final DavException cause = davExceptionNaming(Marker.class.getName());
+        final FileSystemException result = ExceptionConverter.generate(cause);
+        assertNotNull(result);
+        assertFalse(Marker.instantiated, "server-controlled class name must 
not be instantiated");
+    }
+
+    @Test
+    public void testGenerateStillWrapsExceptionType() throws Exception {
+        final DavException cause = 
davExceptionNaming(IOException.class.getName());
+        final FileSystemException result = ExceptionConverter.generate(cause);
+        assertNotNull(result);
+        assertInstanceOf(IOException.class, result.getCause());
+    }
+}

Reply via email to