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());
+ }
+}