feat: support packer config

This commit is contained in:
ReaJason
2026-02-26 22:55:45 +08:00
parent 0d3b56de63
commit 9243070126
85 changed files with 2410 additions and 658 deletions
+1 -4
View File
@@ -5,7 +5,7 @@ plugins {
}
group = "io.github.reajason"
description = "Java deserialize payload for MemShellParty"
description = "Java payload packer for MemShellParty"
version = rootProject.version
dependencies {
@@ -22,9 +22,6 @@ dependencies {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
@@ -1,50 +0,0 @@
package com.reajason.javaweb.packer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author ReaJason
* @since 2025/1/26
*/
public interface AggregatePacker extends Packer {
/**
* 聚合打包当前所有分类下的 payload
*
* @param config 生成结果
* @return key - 打包名称,value - 打包 payload
*/
default Map<String, String> packAll(ClassPackerConfig config) {
return Packers.getPackersWithParent(this.getClass()).stream().collect(Collectors.toMap(
Enum::name,
packers -> {
try {
return packers.getInstance().pack(config);
} catch (Exception e) {
return e.getMessage();
}
},
(existing, replacement) -> existing,
LinkedHashMap::new
));
}
/**
* 将第一个 sub packer 作为默认输出
*
* @param config 生成的内存马信息
* @return payload
*/
@Override
default String pack(ClassPackerConfig config) {
List<Packers> packersWithParent = Packers.getPackersWithParent(this.getClass());
if (packersWithParent.isEmpty()) {
return null;
}
return packersWithParent.get(0).getInstance().pack(config);
}
}
@@ -7,9 +7,10 @@ import lombok.Data;
* @since 2025/6/27
*/
@Data
public class ClassPackerConfig {
public class ClassPackerConfig<T> {
private String className;
private byte[] classBytes;
private String classBytesBase64Str;
private boolean byPassJavaModule;
private T customConfig;
}
@@ -9,7 +9,8 @@ import java.util.Map;
* @since 2025/6/27
*/
@Data
public class JarPackerConfig {
public class JarPackerConfig<T> {
private String mainClassName;
private transient Map<String, byte[]> classBytes;
private T customConfig;
}
@@ -1,10 +1,19 @@
package com.reajason.javaweb.packer;
import com.reajason.javaweb.packer.spec.PackerSchema;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author ReaJason
* @since 2024/11/26
*/
public interface Packer {
public interface Packer<T> {
TypeCache TYPE_CACHE = new TypeCache();
/**
* 将自定义类打包成特定 payload
@@ -12,7 +21,71 @@ public interface Packer {
* @param classPackerConfig 自定义类信息
* @return 字符串 payload
*/
default String pack(ClassPackerConfig classPackerConfig) {
default String pack(ClassPackerConfig<T> classPackerConfig) {
throw new UnsupportedOperationException("当前 " + this.getClass().getSimpleName() + " 不支持 string 生成");
}
}
@SuppressWarnings("unchecked")
default Class<T> customConfigType() {
Optional<Class<?>> resolved = TYPE_CACHE.cache.computeIfAbsent(this.getClass(), TypeResolver::resolveCustomConfigType);
return (Class<T>) resolved.orElse(null);
}
default T resolveCustomConfig(Object rawCustomConfig) {
Class<T> clazz = customConfigType();
if (clazz == null) {
return null;
}
return PackerConfigConverter.convert(rawCustomConfig, clazz);
}
/**
* Packer config schema for UI rendering.
*/
default PackerSchema schema() {
return PackerSchema.empty();
}
final class TypeCache {
private final Map<Class<?>, Optional<Class<?>>> cache = new ConcurrentHashMap<>();
}
final class TypeResolver {
private TypeResolver() {
}
private static Optional<Class<?>> resolveCustomConfigType(Class<?> clazz) {
Class<?> current = clazz;
while (current != null && current != Object.class) {
Optional<Class<?>> fromInterfaces = resolveFromTypes(current.getGenericInterfaces());
if (fromInterfaces.isPresent()) {
return fromInterfaces;
}
current = current.getSuperclass();
}
return Optional.empty();
}
private static Optional<Class<?>> resolveFromTypes(Type[] types) {
for (Type type : types) {
if (!(type instanceof ParameterizedType)) {
continue;
}
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!(parameterizedType.getRawType() instanceof Class)) {
continue;
}
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
if (rawType != Packer.class) {
continue;
}
Type configType = parameterizedType.getActualTypeArguments()[0];
if (configType instanceof Class) {
return Optional.of((Class<?>) configType);
}
return Optional.empty();
}
return Optional.empty();
}
}
}
@@ -0,0 +1,27 @@
package com.reajason.javaweb.packer;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Shared conversion helper for packer custom config.
*/
final class PackerConfigConverter {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private PackerConfigConverter() {
}
static <R> R convert(Object customConfig, Class<R> clazz) {
if (customConfig == null) {
return null;
}
if (clazz.isInstance(customConfig)) {
return clazz.cast(customConfig);
}
try {
return OBJECT_MAPPER.convertValue(customConfig, clazz);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("invalid customConfig for " + clazz.getSimpleName() + ": " + e.getMessage(), e);
}
}
}
@@ -1,10 +1,8 @@
package com.reajason.javaweb.packer;
import com.reajason.javaweb.packer.spec.PackerSchema;
import com.reajason.javaweb.packer.aviator.AviatorPacker;
import com.reajason.javaweb.packer.base64.Base64Packer;
import com.reajason.javaweb.packer.base64.Base64URLEncoded;
import com.reajason.javaweb.packer.base64.DefaultBase64Packer;
import com.reajason.javaweb.packer.base64.GzipBase64Packer;
import com.reajason.javaweb.packer.bsh.BeanShellPacker;
import com.reajason.javaweb.packer.deserialize.hessian.Hessian2Packer;
import com.reajason.javaweb.packer.deserialize.hessian.Hessian2XSLTScriptEnginePacker;
@@ -52,7 +50,9 @@ import com.reajason.javaweb.packer.xmldecoder.XMLDecoderPacker;
import com.reajason.javaweb.packer.xmldecoder.XMLDecoderScriptEnginePacker;
import lombok.Getter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -67,20 +67,14 @@ public enum Packers {
* Base64
*/
Base64(new Base64Packer()),
DefaultBase64(new DefaultBase64Packer(), Base64Packer.class),
Base64URLEncoded(new Base64URLEncoded(), Base64Packer.class),
GzipBase64(new GzipBase64Packer(), Base64Packer.class),
/**
* JSP 打包器
*/
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
@@ -193,7 +187,48 @@ public enum Packers {
this.parentPacker = parentPacker;
}
public static List<Packers> getPackersWithParent(Class<?> parentPacker) {
return Stream.of(Packers.values()).filter(p -> Objects.equals(p.getParentPacker(), parentPacker)).collect(Collectors.toList());
public static Packers fromName(String name) {
if (name == null) {
throw new IllegalArgumentException("packer name is required");
}
try {
return Packers.valueOf(name);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("unknown packer: " + name);
}
}
public String getCategoryName() {
if (parentPacker == null) {
return name();
}
for (Packers packer : Packers.values()) {
if (packer.getParentPacker() == null && packer.getInstance().getClass().equals(parentPacker)) {
return packer.name();
}
}
return name();
}
public boolean hasChildren() {
return Stream.of(Packers.values()).anyMatch(p -> Objects.equals(p.getParentPacker(), this.getInstance().getClass()));
}
public String getOutputKind() {
return instance instanceof JarPacker ? "binary-base64" : "text";
}
public PackerSchema getSchema() {
PackerSchema schema = instance.schema();
return schema == null ? PackerSchema.empty() : schema;
}
public static Map<String, List<Packers>> groupedPackers() {
Map<String, List<Packers>> groupedPackers = new LinkedHashMap<>();
for (Packers packer : Packers.values()) {
String groupName = packer.getCategoryName();
groupedPackers.computeIfAbsent(groupName, k -> new java.util.ArrayList<>()).add(packer);
}
return groupedPackers;
}
}
@@ -3,9 +3,12 @@ package com.reajason.javaweb.packer;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.zip.GZIPOutputStream;
/**
* @author ReaJason
@@ -19,4 +22,13 @@ public class Util {
return IOUtils.toString(Objects.requireNonNull(stream), Charset.defaultCharset());
}
}
@SneakyThrows
public static byte[] gzipCompress(byte[] data) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(data);
}
return out.toByteArray();
}
}
@@ -0,0 +1,37 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.spec.PackerFieldSchema;
import com.reajason.javaweb.packer.spec.PackerFieldType;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.Data;
@Data
public class Base64CustomPackerConfig {
private boolean urlEncoded;
private boolean gzipCompressed;
public static PackerSchema schema() {
PackerFieldSchema urlEncodedField = new PackerFieldSchema();
urlEncodedField.setKey("urlEncoded");
urlEncodedField.setType(PackerFieldType.BOOLEAN);
urlEncodedField.setRequired(false);
urlEncodedField.setDefaultValue(false);
urlEncodedField.setDescription("Enable URL encoding");
urlEncodedField.setDescriptionI18nKey("urlEncoded.desc");
PackerFieldSchema gzipCompressedField = new PackerFieldSchema();
gzipCompressedField.setKey("gzipCompressed");
gzipCompressedField.setType(PackerFieldType.BOOLEAN);
gzipCompressedField.setRequired(false);
gzipCompressedField.setDefaultValue(false);
gzipCompressedField.setDescription("Enable GZIP compression");
gzipCompressedField.setDescriptionI18nKey("gzipCompressed.desc");
PackerSchema schema = new PackerSchema();
schema.getFields().add(urlEncodedField);
schema.getFields().add(gzipCompressedField);
schema.getDefaultConfig().put("urlEncoded", false);
schema.getDefaultConfig().put("gzipCompressed", false);
return schema;
}
}
@@ -1,10 +1,42 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author ReaJason
* @since 2024/12/17
*/
public class Base64Packer implements AggregatePacker {
}
public class Base64Packer implements Packer<Base64CustomPackerConfig> {
@Override
@SneakyThrows
public String pack(ClassPackerConfig<Base64CustomPackerConfig> config) {
Base64CustomPackerConfig customConfig = config.getCustomConfig();
if (Objects.isNull(customConfig)) {
return config.getClassBytesBase64Str();
}
byte[] bytes = config.getClassBytes();
if (customConfig.isGzipCompressed()) {
bytes = Util.gzipCompress(bytes);
}
String base64 = Base64.encodeBase64String(bytes);
if (customConfig.isUrlEncoded()) {
return URLEncoder.encode(base64, StandardCharsets.UTF_8.name());
}
return base64;
}
@Override
public PackerSchema schema() {
return Base64CustomPackerConfig.schema();
}
}
@@ -1,21 +0,0 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* @author ReaJason
* @since 2025/9/2
*/
public class Base64URLEncoded implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return URLEncoder.encode(config.getClassBytesBase64Str(), StandardCharsets.UTF_8.name());
}
}
@@ -1,15 +0,0 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
/**
* @author ReaJason
* @since 2024/12/17
*/
public class DefaultBase64Packer implements Packer {
@Override
public String pack(ClassPackerConfig config) {
return config.getClassBytesBase64Str();
}
}
@@ -1,30 +0,0 @@
package com.reajason.javaweb.packer.base64;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPOutputStream;
/**
* @author ReaJason
* @since 2025/1/22
*/
public class GzipBase64Packer implements Packer {
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return Base64.getEncoder().encodeToString(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();
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class Hessian2Packer implements AggregatePacker {
public class Hessian2Packer implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.Hessian2XSLTScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.deserialize.hessian;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/2/20
*/
public class HessianPacker implements AggregatePacker {
public class HessianPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.HessianXSLTScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.deserialize.java;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/10
*/
public class JavaDeserializePacker implements AggregatePacker {
}
public class JavaDeserializePacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.JavaCommonsBeanutils19.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.groovy;
import com.reajason.javaweb.packer.AggregatePacker;
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 GroovyPacker implements AggregatePacker {
public class GroovyPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.GroovyClassDefiner.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.h2;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/6/28
*/
public class H2Packer implements AggregatePacker {
public class H2Packer implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.H2Javac.getInstance().pack(classPackerConfig);
}
}
@@ -24,7 +24,8 @@ public class DefaultJarPacker implements JarPacker {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (JarOutputStream targetJar = new JarOutputStream(byteArrayOutputStream, manifest)) {
for (Map.Entry<String, byte[]> entry : jarPackerConfig.getClassBytes().entrySet()) {
Map<String, byte[]> classBytes = ((Map<String, byte[]>) jarPackerConfig.getClassBytes());
for (Map.Entry<String, byte[]> entry : classBytes.entrySet()) {
targetJar.putNextEntry(new JarEntry(entry.getKey().replace('.', '/') + ".class"));
targetJar.write(entry.getValue());
targetJar.closeEntry();
@@ -8,6 +8,7 @@ import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
@@ -21,7 +22,7 @@ public class GroovyTransformJarPacker implements JarPacker {
@SneakyThrows
public byte[] packBytes(JarPackerConfig config) {
String mainClassName = config.getMainClassName();
byte[] mainClassBytes = config.getClassBytes().get(mainClassName);
byte[] mainClassBytes = ((Map<String, byte[]>) config.getClassBytes()).get(mainClassName);
mainClassBytes = ClassInterfaceUtils.addInterface(mainClassBytes, "org.codehaus.groovy.transform.ASTTransformation");
mainClassBytes = ClassAnnotationUtils.setAnnotation(mainClassBytes, "org.codehaus.groovy.transform.GroovyASTTransformation");
@@ -7,6 +7,7 @@ import lombok.SneakyThrows;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
@@ -21,7 +22,7 @@ public class ScriptEngineJarPacker implements JarPacker {
@SneakyThrows
public byte[] packBytes(JarPackerConfig config) {
String mainClassName = config.getMainClassName();
byte[] mainClassBytes = config.getClassBytes().get(mainClassName);
byte[] mainClassBytes = ((Map<String, byte[]>) config.getClassBytes()).get(mainClassName);
byte[] bytes = ClassInterfaceUtils.addInterface(mainClassBytes, "javax.script.ScriptEngineFactory");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (JarOutputStream targetJar = new JarOutputStream(outputStream, new Manifest())) {
@@ -3,20 +3,31 @@ package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class ClassLoaderJspPacker implements Packer {
public class ClassLoaderJspPacker implements Packer<JspCustomPackerConfig> {
private final String jspTemplate = Util.loadTemplateFromResource("/memshell-party/shell.jsp");
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return jspTemplate.replace("{{className}}", config.getClassName())
public String pack(ClassPackerConfig<JspCustomPackerConfig> config) {
String content = jspTemplate.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
JspCustomPackerConfig customConfig = config.getCustomConfig();
if (customConfig != null && customConfig.isUnicode()) {
return JspUnicoder.encode(content, true);
}
return content;
}
@Override
public PackerSchema schema() {
return JspCustomPackerConfig.schema();
}
}
@@ -1,12 +0,0 @@
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);
}
}
@@ -3,27 +3,38 @@ package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class DefineClassJspPacker implements Packer {
public class DefineClassJspPacker implements Packer<JspCustomPackerConfig> {
private final String template = Util.loadTemplateFromResource("/memshell-party/shell1.jsp");
private final String bypassTemplate = Util.loadTemplateFromResource("/memshell-party/shell2.jsp");
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
public String pack(ClassPackerConfig<JspCustomPackerConfig> config) {
String injectorBytesBase64Str = config.getClassBytesBase64Str();
String injectorClassName = config.getClassName();
String template = this.template;
if (config.isByPassJavaModule()) {
template = bypassTemplate;
}
return template.replace("{{className}}", injectorClassName)
String content = template.replace("{{className}}", injectorClassName)
.replace("{{base64Str}}", injectorBytesBase64Str);
JspCustomPackerConfig customConfig = config.getCustomConfig();
if (customConfig != null && customConfig.isUnicode()) {
return JspUnicoder.encode(content, true);
}
return content;
}
}
@Override
public PackerSchema schema() {
return JspCustomPackerConfig.schema();
}
}
@@ -1,20 +0,0 @@
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);
}
}
@@ -0,0 +1,30 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.spec.PackerFieldSchema;
import com.reajason.javaweb.packer.spec.PackerFieldType;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.Data;
/**
* @author ReaJason
* @since 2026/2/26
*/
@Data
public class JspCustomPackerConfig {
private boolean unicode;
public static PackerSchema schema() {
PackerFieldSchema unicodeField = new PackerFieldSchema();
unicodeField.setKey("unicode");
unicodeField.setType(PackerFieldType.BOOLEAN);
unicodeField.setRequired(false);
unicodeField.setDefaultValue(false);
unicodeField.setDescription("Enable Unicode encoding");
unicodeField.setDescriptionI18nKey("unicodeEncoded.desc");
PackerSchema schema = new PackerSchema();
schema.getFields().add(unicodeField);
schema.getDefaultConfig().put("unicode", false);
return schema;
}
}
@@ -1,10 +1,16 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class JspPacker implements AggregatePacker {
}
public class JspPacker implements Packer<JspCustomPackerConfig> {
@Override
public String pack(ClassPackerConfig<JspCustomPackerConfig> classPackerConfig) {
return Packers.ClassLoaderJSP.getInstance().pack(classPackerConfig);
}
}
@@ -3,20 +3,31 @@ package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Util;
import com.reajason.javaweb.packer.spec.PackerSchema;
import lombok.SneakyThrows;
/**
* @author ReaJason
* @since 2024/11/26
*/
public class JspxPacker implements Packer {
public class JspxPacker implements Packer<JspCustomPackerConfig> {
private final String jspxTemplate = Util.loadTemplateFromResource("/memshell-party/shell.jspx");
@Override
@SneakyThrows
public String pack(ClassPackerConfig config) {
return jspxTemplate.replace("{{className}}", config.getClassName())
public String pack(ClassPackerConfig<JspCustomPackerConfig> config) {
String content = jspxTemplate.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", config.getClassBytesBase64Str());
JspCustomPackerConfig customConfig = config.getCustomConfig();
if (customConfig != null && customConfig.isUnicode()) {
return JspUnicoder.encode(content, false);
}
return content;
}
}
@Override
public PackerSchema schema() {
return JspCustomPackerConfig.schema();
}
}
@@ -1,13 +0,0 @@
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);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.jxpath;
import com.reajason.javaweb.packer.AggregatePacker;
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 JXPathPacker implements AggregatePacker {
public class JXPathPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.JXPathScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -3,6 +3,8 @@ 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.Util;
import org.apache.commons.codec.binary.Base64;
import static com.reajason.javaweb.packer.spel.SpELSpringGzipJDK17Packer.assertClassNameValid;
@@ -18,6 +20,6 @@ public class JXPathSpringGzipJDK17Packer implements Packer {
String className = config.getClassName();
assertClassNameValid(className);
return template.replace("{{className}}", className)
.replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config));
.replace("{{base64Str}}", Base64.encodeBase64String(Util.gzipCompress(config.getClassBytes())));
}
}
@@ -3,6 +3,8 @@ 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.Util;
import org.apache.commons.codec.binary.Base64;
/**
* @author ReaJason
@@ -14,6 +16,6 @@ public class JXPathSpringGzipPacker implements Packer {
@Override
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config));
.replace("{{base64Str}}", Base64.encodeBase64String(Util.gzipCompress(config.getClassBytes())));
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.ognl;
import com.reajason.javaweb.packer.AggregatePacker;
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 OGNLPacker implements AggregatePacker {
public class OGNLPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.OGNLScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -3,7 +3,9 @@ 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.Util;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import static com.reajason.javaweb.packer.spel.SpELSpringGzipJDK17Packer.assertClassNameValid;
@@ -20,6 +22,6 @@ public class OGNLSpringGzipJDK17Packer implements Packer {
String className = config.getClassName();
assertClassNameValid(className);
return template.replace("{{className}}", className)
.replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config));
.replace("{{base64Str}}", Base64.encodeBase64String(Util.gzipCompress(config.getClassBytes())));
}
}
@@ -3,7 +3,9 @@ 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.Util;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
/**
* @author ReaJason
@@ -16,6 +18,6 @@ public class OGNLSpringGzipPacker implements Packer {
@SneakyThrows
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config));
.replace("{{base64Str}}", Base64.encodeBase64String(Util.gzipCompress(config.getClassBytes())));
}
}
@@ -1,10 +1,16 @@
package com.reajason.javaweb.packer.scriptengine;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2024/12/3
*/
public class ScriptEnginePacker implements AggregatePacker {
public class ScriptEnginePacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.DefaultScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -0,0 +1,20 @@
package com.reajason.javaweb.packer.spec;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* Field metadata for packer config UI.
*/
@Data
public class PackerFieldSchema {
private String key;
private PackerFieldType type;
private boolean required;
private Object defaultValue;
private String description;
private String descriptionI18nKey;
private List<PackerOptionValue> options = new ArrayList<>();
}
@@ -0,0 +1,11 @@
package com.reajason.javaweb.packer.spec;
/**
* Supported packer config field types.
*/
public enum PackerFieldType {
BOOLEAN,
STRING,
ENUM,
INTEGER
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.packer.spec;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Enum option metadata for UI rendering.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PackerOptionValue {
private String value;
private String label;
}
@@ -0,0 +1,15 @@
package com.reajason.javaweb.packer.spec;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Request-time packer selection and config.
*/
@Data
public class PackerRequestSpec {
private String name;
private Map<String, Object> config = new LinkedHashMap<>();
}
@@ -0,0 +1,21 @@
package com.reajason.javaweb.packer.spec;
import lombok.Data;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Packer config schema metadata.
*/
@Data
public class PackerSchema {
private List<PackerFieldSchema> fields = new ArrayList<>();
private Map<String, Object> defaultConfig = new LinkedHashMap<>();
public static PackerSchema empty() {
return new PackerSchema();
}
}
@@ -1,10 +1,16 @@
package com.reajason.javaweb.packer.spel;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/1/26
*/
public class SpELPacker implements AggregatePacker {
public class SpELPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.SpELScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -3,6 +3,8 @@ package com.reajason.javaweb.packer.spel;
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 org.apache.commons.codec.binary.Base64;
/**
* @author ReaJason
@@ -16,7 +18,7 @@ public class SpELSpringGzipJDK17Packer implements Packer {
String className = config.getClassName();
assertClassNameValid(className);
return template.replace("{{className}}", className)
.replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config));
.replace("{{base64Str}}", Base64.encodeBase64String(Util.gzipCompress(config.getClassBytes())));
}
public static void assertClassNameValid(String className) {
@@ -3,6 +3,8 @@ package com.reajason.javaweb.packer.spel;
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 org.apache.commons.codec.binary.Base64;
/**
@@ -19,6 +21,6 @@ public class SpELSpringGzipPacker implements Packer {
@Override
public String pack(ClassPackerConfig config) {
return template.replace("{{className}}", config.getClassName())
.replace("{{base64Str}}", Packers.GzipBase64.getInstance().pack(config));
.replace("{{base64Str}}", Base64.encodeBase64String(Util.gzipCompress(config.getClassBytes())));
}
}
@@ -1,10 +1,16 @@
package com.reajason.javaweb.packer.translet;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/11/19
*/
public class AbstractTransletPacker implements AggregatePacker {
public class AbstractTransletPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.JDKAbstractTransletPacker.getInstance().pack(classPackerConfig);
}
}
@@ -1,11 +1,16 @@
package com.reajason.javaweb.packer.xmldecoder;
import com.reajason.javaweb.packer.AggregatePacker;
import com.reajason.javaweb.packer.ClassPackerConfig;
import com.reajason.javaweb.packer.Packer;
import com.reajason.javaweb.packer.Packers;
/**
* @author ReaJason
* @since 2025/7/22
*/
public class XMLDecoderPacker implements AggregatePacker {
public class XMLDecoderPacker implements Packer {
@Override
public String pack(ClassPackerConfig classPackerConfig) {
return Packers.XMLDecoderScriptEngine.getInstance().pack(classPackerConfig);
}
}
@@ -0,0 +1,91 @@
package com.reajason.javaweb.packer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
class PackerCustomConfigTest {
@Test
void typedPackerShouldInferCustomConfigType() {
TypedDemoPacker packer = new TypedDemoPacker();
Assertions.assertEquals(DemoCustomConfig.class, packer.customConfigType());
}
@Test
void rawPackerShouldTreatAsNoCustomConfig() {
RawDemoPacker packer = new RawDemoPacker();
Assertions.assertNull(packer.customConfigType());
Assertions.assertNull(packer.resolveCustomConfig(Map.of("enabled", true)));
}
@Test
void typedPackerShouldResolveCustomConfig() {
TypedDemoPacker packer = new TypedDemoPacker();
DemoCustomConfig resolved = packer.resolveCustomConfig(Map.of(
"name", "demo",
"enabled", true,
"count", 7
));
Assertions.assertEquals("demo", resolved.getName());
Assertions.assertTrue(resolved.isEnabled());
Assertions.assertEquals(7, resolved.getCount());
}
@Test
void typedPackerShouldReturnDefaultWhenNull() {
TypedDemoPackerWithDefault packer = new TypedDemoPackerWithDefault();
DemoCustomConfig resolved = packer.resolveCustomConfig(null);
Assertions.assertNotNull(resolved);
Assertions.assertEquals("default", resolved.getName());
Assertions.assertEquals(9, resolved.getCount());
}
static class TypedDemoPacker implements Packer<DemoCustomConfig> {
}
static class TypedDemoPackerWithDefault implements Packer<DemoCustomConfig> {
@Override
public DemoCustomConfig defaultCustomConfig() {
DemoCustomConfig config = new DemoCustomConfig();
config.setName("default");
config.setCount(9);
return config;
}
}
static class RawDemoPacker implements Packer {
}
public static class DemoCustomConfig {
private String name;
private boolean enabled;
private int count;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
}
@@ -0,0 +1,75 @@
package com.reajason.javaweb.packer.jsp;
import com.reajason.javaweb.packer.ClassPackerConfig;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Collections;
class JspCustomPackerConfigTest {
@Test
void classLoaderJspShouldApplyUnicodeWhenEnabled() {
ClassLoaderJspPacker packer = new ClassLoaderJspPacker();
ClassPackerConfig<JspCustomPackerConfig> config = buildConfig();
config.setCustomConfig(packer.resolveCustomConfig(null));
String plain = packer.pack(config);
config.setCustomConfig(packer.resolveCustomConfig(Collections.singletonMap("unicode", true)));
String unicode = packer.pack(config);
Assertions.assertEquals(JspUnicoder.encode(plain, true), unicode);
}
@Test
void defineClassJspShouldApplyUnicodeWhenEnabled() {
DefineClassJspPacker packer = new DefineClassJspPacker();
ClassPackerConfig<JspCustomPackerConfig> config = buildConfig();
config.setCustomConfig(packer.resolveCustomConfig(null));
String plain = packer.pack(config);
config.setCustomConfig(packer.resolveCustomConfig(Collections.singletonMap("unicode", true)));
String unicode = packer.pack(config);
Assertions.assertEquals(JspUnicoder.encode(plain, true), unicode);
}
@Test
void jspxShouldApplyUnicodeWhenEnabled() {
JspxPacker packer = new JspxPacker();
ClassPackerConfig<JspCustomPackerConfig> config = buildConfig();
config.setCustomConfig(packer.resolveCustomConfig(null));
String plain = packer.pack(config);
config.setCustomConfig(packer.resolveCustomConfig(Collections.singletonMap("unicode", true)));
String unicode = packer.pack(config);
Assertions.assertEquals(JspUnicoder.encode(plain, false), unicode);
}
@Test
void defaultShouldDisableUnicode() {
ClassLoaderJspPacker packer = new ClassLoaderJspPacker();
ClassPackerConfig<JspCustomPackerConfig> config = buildConfig();
config.setCustomConfig(packer.resolveCustomConfig(null));
String plain = packer.pack(config);
config.setCustomConfig(packer.resolveCustomConfig(Collections.emptyMap()));
Assertions.assertEquals(plain, packer.pack(config));
}
@Test
void invalidUnicodeTypeShouldThrow() {
ClassLoaderJspPacker packer = new ClassLoaderJspPacker();
Assertions.assertThrows(IllegalArgumentException.class,
() -> packer.resolveCustomConfig(Collections.singletonMap("unicode", "oops")));
}
private static ClassPackerConfig<JspCustomPackerConfig> buildConfig() {
ClassPackerConfig<JspCustomPackerConfig> config = new ClassPackerConfig<>();
config.setClassName("hello.world.Injector");
config.setClassBytesBase64Str("QUJDRA==");
config.setClassBytes(new byte[]{1, 2, 3, 4});
return config;
}
}