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-bcel.git
The following commit(s) were added to refs/heads/master by this push:
new 1d7760f3 Class2HTML builds output file paths from the unvalidated
class name (f015).
1d7760f3 is described below
commit 1d7760f302a0dfe089030acbe2b55fceab1980c5
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Sep 4 17:24:14 2026 -0400
Class2HTML builds output file paths from the unvalidated class name
(f015).
---
src/changes/changes.xml | 3 +-
src/main/java/org/apache/bcel/util/Class2HTML.java | 24 +++++++++
.../apache/bcel/util/Class2HTMLTraversalTest.java | 59 ++++++++++++++++++++++
3 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 09084eb2..554a89e0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -101,7 +101,8 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Utility.typeSignatureToString recurses per generic-nesting level,
unbounded, with quadratic substring copies. (f011).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Repositories cache classes under the input-defined this_class name;
global static Repository/VerifierFactory make the poisoning ClassLoader-wide.
(f012).</action>
<action type="fix" dev="ggregory" due-to="Gary
Gregory">Silent u2/u1 count truncation across dump paths corrupts emitted
bytecode. (f013).</action>
- <action type="fix" dev="ggregory" due-to="Gary
Gregory">Class2HTML emitters write attacker class-file strings into HTML
unescaped (stored XSS in reports) (f014).</action>
+ <action type="fix" dev="ggregory" due-to="Gary
Gregory">Class2HTML emitters write attacker class-file strings into HTML
unescaped (stored XSS in reports) (f014).</action>
+ <action type="fix" dev="ggregory" due-to="Gary
Gregory">Class2HTML builds output file paths from the unvalidated class name
(f015).</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="nbauma109,
Gary Gregory">Add support for permitted subclasses #493.</action>
<action type="add" dev="ggregory" due-to="nbauma109,
Gary Gregory">Add RecordComponentInfo.getAttribute(byte tag)#494.</action>
diff --git a/src/main/java/org/apache/bcel/util/Class2HTML.java
b/src/main/java/org/apache/bcel/util/Class2HTML.java
index 2047f7f2..69d010c3 100644
--- a/src/main/java/org/apache/bcel/util/Class2HTML.java
+++ b/src/main/java/org/apache/bcel/util/Class2HTML.java
@@ -215,6 +215,7 @@ public class Class2HTML implements Constants {
this.javaClass = javaClass;
this.dir = dir;
className = javaClass.getClassName(); // Remember full name
+ checkFileNameSafe(className);
constantPool = javaClass.getConstantPool();
// Get package name by tacking off everything after the last '.'
final int index = className.lastIndexOf('.');
@@ -235,6 +236,29 @@ public class Class2HTML implements Constants {
}
}
+ /**
+ * The class name comes from the attacker-controlled this_class constant
of the parsed class file and is
+ * concatenated into the five output file paths ("dir + className +
suffix"). Class file parsing only folds
+ * '/' into '.', so Windows separators ('\\'), drive designators (':') and
".." segments survive and would
+ * let a crafted class file write its HTML output outside the target
directory (CWE-22).
+ *
+ * @param name the class name about to be used as part of a file name.
+ * @throws IOException if the name contains a path separator, a
Windows-reserved file name character, a
+ * control character, or a ".." sequence.
+ */
+ private static void checkFileNameSafe(final String name) throws
IOException {
+ for (int i = 0; i < name.length(); i++) {
+ final char c = name.charAt(i);
+ if (c < ' ' || "\\/:*?\"<>|".indexOf(c) >= 0) {
+ throw new IOException("Refusing to write HTML for a class
whose name contains the unsafe character (0x"
+ + Integer.toHexString(c) + "): " + name);
+ }
+ }
+ if (name.contains("..")) {
+ throw new IOException("Refusing to write HTML for a class whose
name contains \"..\": " + name);
+ }
+ }
+
private void writeMainHTML(final AttributeHTML attributeHtml, final
Charset charset) throws FileNotFoundException, UnsupportedEncodingException {
try (PrintWriter file = new PrintWriter(dir + className + ".html",
charset.name())) {
// @formatter:off
diff --git a/src/test/java/org/apache/bcel/util/Class2HTMLTraversalTest.java
b/src/test/java/org/apache/bcel/util/Class2HTMLTraversalTest.java
new file mode 100644
index 00000000..0a4cf2de
--- /dev/null
+++ b/src/test/java/org/apache/bcel/util/Class2HTMLTraversalTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.bcel.util;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.classfile.JavaClass;
+import org.apache.bcel.generic.ClassGen;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Output file paths are built as "dir + className + suffix" from the
attacker-controlled this_class constant.
+ * Names carrying Windows separators, drive designators or ".." must be
rejected, or a crafted class file writes
+ * its HTML outside the target directory.
+ */
+class Class2HTMLTraversalTest {
+
+ private static File outputDir() {
+ final File outputDir = new File("target/test-output/html-traversal");
+ if (!outputDir.mkdirs()) {
+ assertTrue(outputDir.isDirectory());
+ }
+ return outputDir;
+ }
+
+ @Test
+ void testRejectsClassNameWithDriveDesignator() {
+ final JavaClass jc = new ClassGen("C:pwn", "java.lang.Object",
"pwn.java", Const.ACC_PUBLIC, null).getJavaClass();
+ assertThrows(IOException.class, () -> new Class2HTML(jc,
outputDir().getAbsolutePath() + File.separator));
+ }
+
+ @Test
+ void testRejectsClassNameWithWindowsTraversal() {
+ final JavaClass jc = new ClassGen("..\\..\\pwn", "java.lang.Object",
"pwn.java", Const.ACC_PUBLIC, null).getJavaClass();
+ assertThrows(IOException.class, () -> new Class2HTML(jc,
outputDir().getAbsolutePath() + File.separator));
+ }
+}