Java 反序列化漏洞的危害不仅在于普通小工具所能带来的命令执行,还在于使用链构建的单向代码链所能实现的有限能力,因为Java应用程序场景和小工具大多构建单向代码执行。然而,在大多数情况下,比如需要echo和注入内存shell,我们实际上非常需要直接运行整个类或者运行一个上下文相关的多行代码来进行动态执行。它经常要求反序列化使用一个链来与另一个能够动态执行代码的链合作。这也是我们在这里主要讨论的情况。下面将简要介绍一些相对常见的方法,在这些方法中,下面的部分可以直接在动态代码上下文中执行。
单向代码利用了链的缺点
我们常见的著名Java 反序列化小工具,如系列中的TransformerChain。不久前还有weblogic coherence的反射提取器。虽然它们大多数存在于oracle产品中,但是这个链的效果与TransformerChain相当,这非常有趣。这里没有过多描述具体的原理。在分析和使用过程中,我们可以发现,因为它们的执行属于一个可以返回对象并再次进行对象调用的链式结构。所以我们不能在中途为相应的执行代码做任何额外的事情,这就是为什么它变成了一个链。它的下一个执行方法受到上一步返回的对象的限制。
举一个简单的例子,我们经常使用这种小工具进行反射调用来执行相应的方法。首先,它有一个条件,我们可以编写的普通代码是一个链结构,例如:
Runtime.getRuntime()。exec(' ls ');
如果相应类中getRuntime方法的描述符指定的访问权限不是公共的,那么我们不能直接调用。此时,我们需要在获取类之后,通过使用中间的反射式单步AccessibleObject的setAccessible来取消默认的Java语言访问控制检查,但是它不返回值,因此不能在链中使用。因此,单向代码利用链不能执行关联的上下文代码。这种操作在对象内部属性的添加、删除和修改中很常见。缺少返回值意味着我们不能构建一个有效的执行链。
另一种情况是,如果相应的方法想要传入指定类型的参数对象,这不仅意味着存在上述问题,而且如果被处理的对象需要满足对象支持反序列化的条件此外,在需要回应或做一些额外事情的情况下,它是非常有限的,因为我们不仅需要可执行代码上下文,而且期望在当前的执行环境中获得上下文。实际的关键是,我们需要在当前的执行环境中使用类加载器来加载我们需要运行的代码,或者通过其他脚本引擎来加载代码。
单向代码利用了链的缺点,也就是说,为什么我们需要动态代码上下文执行?事实上,它的核心是使用Java来动态编译和加载。安全性通常在jsp shell的旁路中找到。当然,由于单向执行链的前提条件,没有比jsp shell更多的姿态,但是有一些额外的链匹配其他类库。
动态代码上下文执行链
这里的简短共享部分可以通过单向代码执行链和一些小原则来执行动态代码,也欢迎添加。没有讨论多次执行和文件登陆的必要性。
ClassLoader
loadClassass
这里没有特别提到类装入器的相关知识。类加载的相关知识可以自己查阅。事实上,可以直接使用的常用类加载器并不多,因为其最终操作的核心需要触发loadClass。为了正常地完成父委托过程,有必要定义parentClassLoader,除非它要启动类加载器或中断父委托。因此,在loadClass可以运行之前,需要定义当前的classLoader实例(即设置了parentClassLoader)。类加载器的大多数第三方实现要求用户自己获取和传递自定义或上下文类加载器对象,例如:
同时ClassLoader类也不支持序列化因此也无法传入,所以我们要找到直接可以调用的就需要满足它运行时自己获取了上下文的classLoader而不用我们传入。
比如:
org.mozilla.classfile.DefiningClassLoader
groovy.lang.GroovyClassLoader
com.sun.org.apache.bcel.internal.util.ClassLoader……
defineClass
前面讲了利用ClassLoader的首个条件,对于ClassLoader来获取class,它必经之路就是loadClass方法。即使很多人说使用defineClass也可以构造一个class,但它实际上也会走次loadClass,我们可以简单过下原理。
首先加载类的话它会先尝试加载父类。当我们调用defineClass的时候,它会走到Native方法defineClass1
ClassLoader.c#Java_java_lang_ClassLoader_defineClass1() -> jvm.cpp#JVM_DefineClassWithSource() -> jvm.cpp#jvm_define_class_common()
// common code for JVM_DefineClass() and JVM_DefineClassWithSource()
// and JVM_DefineClassWithSourceCond()
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
jobject loader, const jbyte *buf,
jsize len, jobject pd, const char *source,
jboolean verify, TRAPS) {
if (source == NULL) source = "__JVM_DefineClass__";
assert(THREAD->is_Java_thread(), "must be a JavaThread");
JavaThread* jt = (JavaThread*) THREAD;
PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(),
ClassLoader::perf_define_appclass_selftime(),
ClassLoader::perf_define_appclasses(),
jt->get_thread_stat()->perf_recursion_counts_addr(),
jt->get_thread_stat()->perf_timers_addr(),
PerfClassTraceTime::DEFINE_CLASS);
if (UsePerfData) {
ClassLoader::perf_app_classfile_bytes_read()->inc(len);
}
// Since exceptions can be thrown, class initialization can take place
// if name is NULL no check for class name in .class stream has to be made.
TempNewSymbol class_name = NULL;
if (name != NULL) {
const int str_len = (int)strlen(name);
if (str_len > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
THROW_MSG_0(vmSymbols