From c0c78c4372f36be7386f2845e6fbdc985e408abe Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 17 Jan 2026 14:45:42 +0800 Subject: [PATCH] feat: add jsp unicode packer --- .../javaweb/integration/ShellAssertion.java | 6 +- .../com/reajason/javaweb/packer/Packers.java | 8 +- .../jsp/ClassLoaderJspUnicodePacker.java | 12 +++ .../jsp/DefineClassJspUnicodePacker.java | 20 +++++ .../javaweb/packer/jsp/JspUnicoder.java | 84 +++++++++++++++++++ .../javaweb/packer/jsp/JspxUnicodePacker.java | 13 +++ 6 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/jsp/ClassLoaderJspUnicodePacker.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/jsp/DefineClassJspUnicodePacker.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/jsp/JspUnicoder.java create mode 100644 packer/src/main/java/com/reajason/javaweb/packer/jsp/JspxUnicodePacker.java 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 7344276d..cd4ae2cc 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 @@ -400,16 +400,16 @@ public class ShellAssertion { public static void injectIsOk(String url, String shellType, String shellTool, String content, Packers packer, GenericContainer container) { switch (packer) { - case JSP, ClassLoaderJSP, DefineClassJSP -> { + case JSP, ClassLoaderJSP, ClassLoaderJSPUnicode, DefineClassJSP, DefineClassJSPUnicode -> { String uploadEntry = url + "/upload"; String filename = shellType + shellTool + packer + ".jsp"; String shellUrl = url + "/" + filename; VulTool.uploadJspFileToServer(uploadEntry, filename, content); VulTool.urlIsOk(shellUrl); } - case JSPX -> { + case JSPX, JSPXUnicode -> { String uploadEntry = url + "/upload"; - String filename = shellType + shellTool + ".jspx"; + String filename = shellType + shellTool + packer + ".jspx"; String shellUrl = url + "/" + filename; VulTool.uploadJspFileToServer(uploadEntry, filename, content); VulTool.urlIsOk(shellUrl); 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 c17a0818..2621c34d 100644 --- a/packer/src/main/java/com/reajason/javaweb/packer/Packers.java +++ b/packer/src/main/java/com/reajason/javaweb/packer/Packers.java @@ -23,10 +23,7 @@ import com.reajason.javaweb.packer.h2.H2Packer; import com.reajason.javaweb.packer.jar.*; import com.reajason.javaweb.packer.jexl.JEXLPacker; import com.reajason.javaweb.packer.jinjava.JinJavaPacker; -import com.reajason.javaweb.packer.jsp.ClassLoaderJspPacker; -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.jsp.*; import com.reajason.javaweb.packer.jxpath.JXPathPacker; import com.reajason.javaweb.packer.jxpath.JXPathScriptEnginePacker; import com.reajason.javaweb.packer.jxpath.JXPathSpringGzipJDK17Packer; @@ -79,8 +76,11 @@ public enum Packers { */ JSP(new JspPacker()), ClassLoaderJSP(new ClassLoaderJspPacker(), JspPacker.class), + ClassLoaderJSPUnicode(new ClassLoaderJspUnicodePacker(), JspPacker.class), DefineClassJSP(new DefineClassJspPacker(), JspPacker.class), + DefineClassJSPUnicode(new DefineClassJspUnicodePacker(), JspPacker.class), JSPX(new JspxPacker(), JspPacker.class), + JSPXUnicode(new JspxUnicodePacker(), JspPacker.class), /** * BigInteger diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jsp/ClassLoaderJspUnicodePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jsp/ClassLoaderJspUnicodePacker.java new file mode 100644 index 00000000..ffb49f30 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jsp/ClassLoaderJspUnicodePacker.java @@ -0,0 +1,12 @@ +package com.reajason.javaweb.packer.jsp; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +public class ClassLoaderJspUnicodePacker implements Packer { + @Override + public String pack(ClassPackerConfig classPackerConfig) { + return JspUnicoder.encode(Packers.ClassLoaderJSP.getInstance().pack(classPackerConfig), true); + } +} diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jsp/DefineClassJspUnicodePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jsp/DefineClassJspUnicodePacker.java new file mode 100644 index 00000000..709c42c5 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jsp/DefineClassJspUnicodePacker.java @@ -0,0 +1,20 @@ +package com.reajason.javaweb.packer.jsp; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; +import com.reajason.javaweb.packer.Util; +import lombok.SneakyThrows; + +/** + * @author ReaJason + * @since 2024/11/26 + */ +public class DefineClassJspUnicodePacker implements Packer { + + @Override + @SneakyThrows + public String pack(ClassPackerConfig config) { + return JspUnicoder.encode(Packers.DefineClassJSP.getInstance().pack(config), true); + } +} \ No newline at end of file diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jsp/JspUnicoder.java b/packer/src/main/java/com/reajason/javaweb/packer/jsp/JspUnicoder.java new file mode 100644 index 00000000..ef51d957 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jsp/JspUnicoder.java @@ -0,0 +1,84 @@ +package com.reajason.javaweb.packer.jsp; + +import org.apache.commons.codec.binary.Hex; + +import java.nio.charset.StandardCharsets; + +public class JspUnicoder { + + public static String encode(String content, boolean isJsp) { + if (content == null) { + return null; + } + StringBuilder result = new StringBuilder(content.length()); + int lineStart = 0; + int length = content.length(); + for (int i = 0; i < length; i++) { + if (content.charAt(i) == '\n') { + appendEncodedLine(result, content.substring(lineStart, i), isJsp); + result.append('\n'); + lineStart = i + 1; + } + } + if (lineStart <= length) { + appendEncodedLine(result, content.substring(lineStart), isJsp); + } + return result.toString(); + } + + private static void appendEncodedLine(StringBuilder output, String line, boolean isJsp) { + if (shouldSkipLine(line, isJsp)) { + output.append(line); + return; + } + if (line.contains("page import") || line.contains("page pageEncoding") || line.contains("page contentType")) { + int firstQuote = line.indexOf('"'); + int lastQuote = line.lastIndexOf('"'); + if (firstQuote != -1 && lastQuote > firstQuote) { + String oldStr = line.substring(firstQuote + 1, lastQuote); + String encoded = encodeWordChars(oldStr); + output.append(line, 0, firstQuote + 1) + .append(encoded) + .append(line.substring(lastQuote)); + return; + } + } + output.append(encodeWordChars(line)); + } + + private static boolean shouldSkipLine(String line, boolean isJsp) { + if (line == null) { + return false; + } + if (!isJsp && (line.contains("jsp:root") + || line.contains("jsp:declaration") + || line.contains("jsp:scriptlet") + || line.contains("jsp:directive.page"))) { + return true; + } + return false; + } + + private static String encodeWordChars(String input) { + StringBuilder encoded = new StringBuilder(input.length() * 4); + for (int i = 0; i < input.length(); i++) { + char ch = input.charAt(i); + if (isWordChar(ch)) { + encoded.append(toUnicodeEscape(ch)); + } else { + encoded.append(ch); + } + } + return encoded.toString(); + } + + private static boolean isWordChar(char ch) { + return Character.isLetterOrDigit(ch) || ch == '_'; + } + + private static String toUnicodeEscape(char ch) { + byte[] bytes = String.valueOf(ch).getBytes(StandardCharsets.UTF_8); + return "\\u00" + Hex.encodeHexString(bytes); + } +} + diff --git a/packer/src/main/java/com/reajason/javaweb/packer/jsp/JspxUnicodePacker.java b/packer/src/main/java/com/reajason/javaweb/packer/jsp/JspxUnicodePacker.java new file mode 100644 index 00000000..80b327a0 --- /dev/null +++ b/packer/src/main/java/com/reajason/javaweb/packer/jsp/JspxUnicodePacker.java @@ -0,0 +1,13 @@ +package com.reajason.javaweb.packer.jsp; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import com.reajason.javaweb.packer.Packer; +import com.reajason.javaweb.packer.Packers; + +public class JspxUnicodePacker implements Packer { + @Override + public String pack(ClassPackerConfig classPackerConfig) { + String content = Packers.JSPX.getInstance().pack(classPackerConfig); + return JspUnicoder.encode(content, false); + } +}