From c8de235aaded31a2b75edc4460c5dbfa583f1948 Mon Sep 17 00:00:00 2001 From: rootvector2 Date: Tue, 9 Jun 2026 18:01:45 +0530 Subject: [PATCH] escape constant pool names in generated html --- .../org/apache/bcel/util/AttributeHTML.java | 4 +- .../java/org/apache/bcel/util/Class2HTML.java | 6 +- .../java/org/apache/bcel/util/CodeHTML.java | 6 +- .../org/apache/bcel/util/ConstantHTML.java | 10 +++- .../java/org/apache/bcel/util/MethodHTML.java | 2 +- .../apache/bcel/util/Class2HTMLXSSTest.java | 59 +++++++++++++++++++ 6 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/apache/bcel/util/Class2HTMLXSSTest.java diff --git a/src/main/java/org/apache/bcel/util/AttributeHTML.java b/src/main/java/org/apache/bcel/util/AttributeHTML.java index e0bafaf254..f87b1b24d4 100644 --- a/src/main/java/org/apache/bcel/util/AttributeHTML.java +++ b/src/main/java/org/apache/bcel/util/AttributeHTML.java @@ -159,7 +159,7 @@ void writeAttribute(final Attribute attribute, final String anchor, final int me signature = Utility.signatureToString(signature, false); final int start = var.getStartPC(); final int end = start + var.getLength(); - printWriter.println("
  • " + Class2HTML.referenceType(signature) + " " + var.getName() + " in slot %" + var.getIndex() + printWriter.println("
  • " + Class2HTML.referenceType(signature) + " " + Class2HTML.toHTML(var.getName()) + " in slot %" + var.getIndex() + "
    Valid from lines " + start + " to " + end + "
  • "); }); @@ -173,7 +173,7 @@ void writeAttribute(final Attribute attribute, final String anchor, final int me final String access; index = clazz.getInnerNameIndex(); if (index > 0) { - name = constantPool.getConstantUtf8(index).getBytes(); + name = Class2HTML.toHTML(constantPool.getConstantUtf8(index).getBytes()); } else { name = "<anonymous>"; } diff --git a/src/main/java/org/apache/bcel/util/Class2HTML.java b/src/main/java/org/apache/bcel/util/Class2HTML.java index 3a427ffc9a..b0ced24d22 100644 --- a/src/main/java/org/apache/bcel/util/Class2HTML.java +++ b/src/main/java/org/apache/bcel/util/Class2HTML.java @@ -135,7 +135,7 @@ static String referenceClass(final int index) { String str = constantPool.getConstantString(index, Const.CONSTANT_Class); str = Utility.compactClassName(str); str = Utility.compactClassName(str, classPackage + ".", true); - return "" + str + ""; + return "" + toHTML(str) + ""; } static String referenceType(final String type) { @@ -150,7 +150,7 @@ static String referenceType(final String type) { if (basicTypes.contains(baseType)) { return "" + type + ""; } - return "" + shortType + ""; + return "" + toHTML(shortType) + ""; } static String toHTML(final String str) { @@ -221,7 +221,7 @@ private void writeMainHTML(final AttributeHTML attributeHtml, final Charset char try (PrintWriter file = new PrintWriter(dir + className + ".html", charset.name())) { // @formatter:off file.println("\n" - + "Documentation for " + className + "\n" + + "Documentation for " + toHTML(className) + "\n" + "\n" + "\n" + "").append(fieldName) - .append("\n"); + buf.append("") + .append(Class2HTML.toHTML(fieldName)).append("\n"); } else { - buf.append(constantHtml.referenceConstant(classIndex)).append(".").append(fieldName); + buf.append(constantHtml.referenceConstant(classIndex)).append(".").append(Class2HTML.toHTML(fieldName)); } break; /* diff --git a/src/main/java/org/apache/bcel/util/ConstantHTML.java b/src/main/java/org/apache/bcel/util/ConstantHTML.java index 081baf0a59..a85e1cd3e2 100644 --- a/src/main/java/org/apache/bcel/util/ConstantHTML.java +++ b/src/main/java/org/apache/bcel/util/ConstantHTML.java @@ -123,6 +123,7 @@ private void writeConstant(final int index) { final String methodClass = constantPool.constantToString(classIndex, Const.CONSTANT_Class); String shortMethodClass = Utility.compactClassName(methodClass); // I.e., remove java.lang. shortMethodClass = Utility.compactClassName(shortMethodClass, classPackage + ".", true); // Remove class package prefix + shortMethodClass = Class2HTML.toHTML(shortMethodClass); // Get method signature final ConstantNameAndType c2 = constantPool.getConstant(nameIndex, Const.CONSTANT_NameAndType, ConstantNameAndType.class); final String signature = constantPool.constantToString(c2.getSignatureIndex(), Const.CONSTANT_Utf8); @@ -159,14 +160,16 @@ private void writeConstant(final int index) { final String fieldClass = constantPool.constantToString(classIndex, Const.CONSTANT_Class); String shortFieldClass = Utility.compactClassName(fieldClass); // I.e., remove java.lang. shortFieldClass = Utility.compactClassName(shortFieldClass, classPackage + ".", true); // Remove class package prefix + shortFieldClass = Class2HTML.toHTML(shortFieldClass); final String fieldName = constantPool.constantToString(nameIndex, Const.CONSTANT_NameAndType); + final String htmlFieldName = Class2HTML.toHTML(fieldName); if (fieldClass.equals(className)) { - ref = "" + fieldName + ""; + ref = "" + htmlFieldName + ""; } else { - ref = "" + shortFieldClass + "." + fieldName + "\n"; + ref = "" + shortFieldClass + "." + htmlFieldName + "\n"; } constantRef[index] = "" + shortFieldClass + "." + fieldName + ""; + + className + "_cp.html#cp" + index + "\" TARGET=ConstantPool>" + htmlFieldName + ""; printWriter.println("

    " + ref + "
    \n" + "

    "); break; @@ -176,6 +179,7 @@ private void writeConstant(final int index) { final String className2 = constantPool.constantToString(index, tag); // / -> . String shortClassName = Utility.compactClassName(className2); // I.e., remove java.lang. shortClassName = Utility.compactClassName(shortClassName, classPackage + ".", true); // Remove class package prefix + shortClassName = Class2HTML.toHTML(shortClassName); ref = "" + shortClassName + ""; constantRef[index] = "" + shortClassName + ""; printWriter.println("

    " + ref + "

    \n"); diff --git a/src/main/java/org/apache/bcel/util/MethodHTML.java b/src/main/java/org/apache/bcel/util/MethodHTML.java index ee7d093a39..d6c053dec2 100644 --- a/src/main/java/org/apache/bcel/util/MethodHTML.java +++ b/src/main/java/org/apache/bcel/util/MethodHTML.java @@ -78,7 +78,7 @@ private void writeField(final Field field) { final Attribute[] attributes; access = Utility.replace(access, " ", " "); printWriter.print("" + access + "\n" + Class2HTML.referenceType(type) + "" + name + ""); + + name + "\">" + Class2HTML.toHTML(name) + ""); attributes = field.getAttributes(); // Write them to the Attributes.html file with anchor "[]" for (int i = 0; i < attributes.length; i++) { diff --git a/src/test/java/org/apache/bcel/util/Class2HTMLXSSTest.java b/src/test/java/org/apache/bcel/util/Class2HTMLXSSTest.java new file mode 100644 index 0000000000..478060a921 --- /dev/null +++ b/src/test/java/org/apache/bcel/util/Class2HTMLXSSTest.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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +import org.apache.bcel.Const; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.generic.ClassGen; +import org.apache.bcel.generic.FieldGen; +import org.apache.bcel.generic.Type; +import org.junit.jupiter.api.Test; + +class Class2HTMLXSSTest { + + /** + * A field name in the constant pool is attacker controlled and may contain HTML metacharacters; the generated + * documentation must escape it in text context rather than emit it raw. + */ + @Test + void testFieldNameIsEscaped() throws Exception { + final ClassGen cg = new ClassGen("Evil", "java.lang.Object", "Evil.java", Const.ACC_PUBLIC, null); + cg.addField(new FieldGen(Const.ACC_PUBLIC, Type.INT, "x", cg.getConstantPool()).getField()); + final JavaClass jc = cg.getJavaClass(); + + final File outputDir = new File("target/test-output/html-xss"); + if (!outputDir.mkdirs()) { + assertTrue(outputDir.isDirectory()); + } + new Class2HTML(jc, outputDir.getAbsolutePath() + File.separator); + + final String methods = new String(Files.readAllBytes(new File(outputDir, "Evil_methods.html").toPath()), StandardCharsets.UTF_8); + // The field name rendered as link text must be escaped, not emitted as a live tag. + assertFalse(methods.contains("\">x