fix: SpringBoot Tomcat Shell Inject failed

This commit is contained in:
ReaJason
2025-04-23 00:10:48 +08:00
parent 2851c5d9e0
commit 98281f0eae
6 changed files with 40 additions and 23 deletions
@@ -58,16 +58,16 @@ public class AgentJarPacker implements JarPacker {
@SneakyThrows @SneakyThrows
private void addDependencies(JarOutputStream targetJar, String relocatePrefix, boolean isAsm) { private void addDependencies(JarOutputStream targetJar, String relocatePrefix, boolean isAsm) {
if (isAsm) { if (isAsm) {
addDependency(targetJar, Opcodes.class, true, relocatePrefix); addDependency(targetJar, Opcodes.class, relocatePrefix);
} else { } else {
addDependency(targetJar, ByteBuddy.class, false, relocatePrefix); addDependency(targetJar, ByteBuddy.class, relocatePrefix);
} }
} }
@SneakyThrows @SneakyThrows
private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult, private void addClassesToJar(JarOutputStream targetJar, GenerateResult generateResult,
String relocatePrefix, boolean isRelocateEnabled) { String relocatePrefix, boolean isAsm) {
String dependencyPackage = isRelocateEnabled ? String dependencyPackage = isAsm ?
Opcodes.class.getPackage().getName() : ByteBuddy.class.getPackage().getName(); Opcodes.class.getPackage().getName() : ByteBuddy.class.getPackage().getName();
// Add injector class // Add injector class
@@ -75,16 +75,14 @@ public class AgentJarPacker implements JarPacker {
generateResult.getInjectorClassName(), generateResult.getInjectorClassName(),
generateResult.getInjectorBytes(), generateResult.getInjectorBytes(),
dependencyPackage, dependencyPackage,
relocatePrefix, relocatePrefix);
isRelocateEnabled);
// Add shell class // Add shell class
addClassEntry(targetJar, addClassEntry(targetJar,
generateResult.getShellClassName(), generateResult.getShellClassName(),
generateResult.getShellBytes(), generateResult.getShellBytes(),
dependencyPackage, dependencyPackage,
relocatePrefix, relocatePrefix);
isRelocateEnabled);
// Add inner classes // Add inner classes
for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) { for (Map.Entry<String, byte[]> entry : generateResult.getInjectorInnerClassBytes().entrySet()) {
@@ -92,24 +90,21 @@ public class AgentJarPacker implements JarPacker {
entry.getKey(), entry.getKey(),
entry.getValue(), entry.getValue(),
dependencyPackage, dependencyPackage,
relocatePrefix, relocatePrefix);
isRelocateEnabled);
} }
} }
@SneakyThrows @SneakyThrows
private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes, private void addClassEntry(JarOutputStream targetJar, String className, byte[] classBytes,
String dependencyPackage, String relocatePrefix, boolean isRelocateEnabled) { String dependencyPackage, String relocatePrefix) {
targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class")); targetJar.putNextEntry(new JarEntry(className.replace('.', '/') + ".class"));
byte[] processedBytes = isRelocateEnabled ? byte[] processedBytes = ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage);
ClassRenameUtils.relocateClass(classBytes, dependencyPackage, relocatePrefix + dependencyPackage) :
classBytes;
targetJar.write(processedBytes); targetJar.write(processedBytes);
targetJar.closeEntry(); targetJar.closeEntry();
} }
@SneakyThrows @SneakyThrows
public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, boolean relocate, String relocatePrefix) { public static void addDependency(JarOutputStream targetJar, Class<?> baseClass, String relocatePrefix) {
String packageToMove = baseClass.getPackage().getName().replace('.', '/'); String packageToMove = baseClass.getPackage().getName().replace('.', '/');
URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation(); URL sourceUrl = baseClass.getProtectionDomain().getCodeSource().getLocation();
String sourceUrlString = sourceUrl.toString(); String sourceUrlString = sourceUrl.toString();
@@ -133,13 +128,13 @@ public class AgentJarPacker implements JarPacker {
if (entryName.startsWith(packageToMove)) { if (entryName.startsWith(packageToMove)) {
InputStream entryStream = sourceJar.getInputStream(entry); InputStream entryStream = sourceJar.getInputStream(entry);
byte[] bytes = IOUtils.toByteArray(entryStream); byte[] bytes = IOUtils.toByteArray(entryStream);
if (relocate) { if (entryName.endsWith(".class")) {
targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName)); targetJar.putNextEntry(new JarEntry(relocatePrefix + entryName));
if (bytes.length > 0) { if (bytes.length > 0) {
bytes = ClassRenameUtils.relocateClass(bytes, packageToMove, relocatePrefix + packageToMove); bytes = ClassRenameUtils.relocateClass(bytes, packageToMove, relocatePrefix + packageToMove);
} }
} else { } else {
targetJar.putNextEntry(new JarEntry(entryName)); targetJar.putNextEntry(entry);
} }
targetJar.write(bytes); targetJar.write(bytes);
targetJar.closeEntry(); targetJar.closeEntry();
@@ -82,4 +82,25 @@ public class SpringBoot2ContainerTest {
log.info("container started, app url is : {}", url); log.info("container started, app url is : {}", url);
return url; return url;
} }
static Stream<Arguments> tomcatCasesProvider() {
Server server = Server.Tomcat;
List<String> supportedShellTypes = List.of(
ShellType.FILTER,
// ShellType.LISTENER,
ShellType.VALVE,
ShellType.WEBSOCKET,
ShellType.AGENT_FILTER_CHAIN,
ShellType.AGENT_FILTER_CHAIN_ASM,
ShellType.CATALINA_AGENT_CONTEXT_VALVE,
ShellType.CATALINA_AGENT_CONTEXT_VALVE_ASM);
List<Packers> testPackers = List.of(Packers.ScriptEngine, Packers.SpEL, Packers.Base64);
return TestCasesProvider.getTestCases(imageName, server, supportedShellTypes, testPackers);
}
@ParameterizedTest(name = "{0}|{1}{2}|{3}")
@MethodSource("tomcatCasesProvider")
void testTomcat(String imageName, String shellType, ShellTool shellTool, Packers packer) {
testShellInjectAssertOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V1_8, packer, container, python);
}
} }
@@ -36,7 +36,7 @@ public class ClassRenameUtils {
} }
String oldClassName = relocateClassPackage.replace('.', '/'); String oldClassName = relocateClassPackage.replace('.', '/');
String newClassName = relocatePrefix.replace('.', '/'); String newClassName = relocatePrefix.replace('.', '/');
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ClassRemapper adapter = new ClassRemapper(writer, new Remapper() { ClassRemapper adapter = new ClassRemapper(writer, new Remapper() {
@Override @Override
public String map(String typeName) { public String map(String typeName) {
@@ -98,8 +98,8 @@ public class TomcatFilterInjector {
Object filterMap; Object filterMap;
try { try {
// tomcat v8/9 // tomcat v8/9
filterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef").newInstance(); filterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef", true, context.getClass().getClassLoader()).newInstance();
filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap").newInstance(); filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap", true, context.getClass().getClassLoader()).newInstance();
} catch (Exception e2) { } catch (Exception e2) {
// tomcat v6/7 // tomcat v6/7
try { try {
@@ -119,7 +119,7 @@ public class TomcatFilterInjector {
Constructor<?>[] constructors; Constructor<?>[] constructors;
try { try {
invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(filterMap, "addURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig").getDeclaredConstructors(); constructors = Class.forName("org.apache.catalina.core.ApplicationFilterConfig", true, context.getClass().getClassLoader()).getDeclaredConstructors();
} catch (Exception e) { } catch (Exception e) {
// tomcat v5 // tomcat v5
invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()}); invokeMethod(filterMap, "setURLPattern", new Class[]{String.class}, new Object[]{getUrlPattern()});
+1
View File
@@ -16,6 +16,7 @@ repositories {
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'commons-io:commons-io:2.+' implementation 'commons-io:commons-io:2.+'
implementation 'net.bytebuddy:byte-buddy:1.10.10'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
} }
@@ -1,9 +1,7 @@
import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
/** /**
@@ -14,9 +12,11 @@ public class TestServlet extends HttpServlet {
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getMethod();
} }
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getMethod();
} }
} }