Hessian反序列化学习
RPC
RPC(Remote Procedure Call Protocol)远程过程调用协议,RPC与RMI类似都是通过网络调用远程的服务。但RPC和RMI的不同之处就在于它以标准的二进制格式来定义请求的信息 ( 请求的对象、方法、参数等 ),这种方式传输信息的优点之一就是跨语言及操作系统。
在面向对象编程范式中,RMI其实就是RPC的一种具体实现。
RPC协议的一次远程通信过程如下:
- 客户端发起请求,并按照RPC协议格式填充信息
- 填充完毕后将二进制格式文件转化为流,通过传输协议进行传输
- 服务端接收到流后,将其转换为二进制格式文件,并按照RPC协议格式获取请求的信息并进行处理
- 处理完毕后将结果按照RPC协议格式写入二进制格式文件中并返回
Hessian简介和基本使用方法
- Hessian协议
Hessian是一个基于RPC的高性能二进制远程传输协议,官方在不同语言中(Java、Python、C++…)均有对Hessian协议的实现,Hessian一般在Web服务中是用。在Java中Hessian协议定义远程对象,是用二进制格式进行传输。
- 基本是用
导入依赖
1 | <dependencies> |
使用Hessian和Java原生的序列化和反序列化的区别
1 | package org.example; |
Hessian反序列化
接Rome打JNDI注入(出网)
demo,打Rome然后JdbcRowSetImpl JNDI注入
1 | package org.example; |
使用Hessian输入输出流封装Hashmap对象后,首先是HessianInput的readObject
首先跟进封装的类型进入switch中对应的case,此处为77(map)
获取type然后读取map
在readMap中获取Deserializer对象传入_hashMapDeserializer
,然后从input中读取map
HashMap,然后运行构造函数实例
继续跟进是反射的代码模块了,首先获取access的权限,然后创建实例
然后addRef新生成的map,这里恢复hashmap的结构时循环in中的内容,然后直接调用了readObject来恢复对象,同时put到hashmap中
put -> hash -> hashcode
,然后进入rome链子
1 | toString:137, ToStringBean (com.sun.syndication.feed.impl) |
之后就是触发getter,pds中的databaseMetaData.getMetaData中进行lookup查询,然后完成JNDI注入
总的调用堆栈
1 | connect:634, JdbcRowSetImpl (com.sun.rowset) |
TemplatesImpl+SignedObject二次反序列化
在JNDI中曾尝试了触发TemplatesImpl链,因为毕竟比较简单
但是失败了,demo如下:
1 | String filePath = "E:\\Java_Sec\\HessianDemo\\src\\main\\java\\org\\example\\Evil.class"; |
没有报错,但是也没有预期中的弹计算器
调试
恶意TemplatesImpl套在ToStringBean中,ToStringBean套在EqualsBean中,所以在反序列化时会如同按字典映射关系进行反序列化
1 | readMap:220, UnsafeDeserializer (com.caucho.hessian.io) [3] |
注意此处在恢复TempaltesImpl对象的时候,使用封装的deser进行反序列化,如下图,_tfactory
为null,与我们预设的不同
从而导致了在后续调用时null字段调用了getter导致了Null异常
查看了下对应的声明,这些字段均有由transient修饰
transient的作用是指定某个类的字段在序列化时不被序列化
二次反序列化
看看TemplatesImpl的原生反序列化函数,他会在原生的readObject中帮助声明工厂字段
因此,二次反序列化的打法应运而生。
二次反序列化:在一个类中有字段被transient修饰,在正常序列化中无法被序列化,但是其原生的readObject中有对应的声明
因此如果我们能找到一条链子A,其含有任意对象的readObject函数,那么先触发链A,在链A中将对象设置为sink对象的,那么就可以执行原生反序列化,从而恢复完整的对象
此处找到的对象为java.security的SignedObject
1 | getObject:178, SignedObject (java.security) |
然后恢复出Templates的完整对象
1 | readObject:199, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) |
最好在Rome的toString中会循环调用getter,那么就可以拼接到TemplatesImpl的getOutputProperties了
1 | getOutputProperties:439, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) |
其他链子
Spring PartiallyComparableAdvisorHolder链
Spring AbstractBeanFactoryPointcutAdvisor链
Resin链
XBean链
Apache Dubbo Hessian反序列化漏洞(CVE-2020-1948)
Apache Dubbo 是一款高性能的开源Java RPC框架,支持多种传输协议,例如dubbo(Dubbo Hessian2)、Hessian、RMI、HTTP等。在某些版本下,Apache Dubbo默认使用hessian,其存在反序列化漏洞,攻击者可以利用发送恶意RPC请求来触发该漏洞。
Apache Dubbo工作流程:
- 首先服务容器加载并允许Provider
- Provider在启动时向注册中心Registry注册自己提供的服务
- Consumer在Registry处订阅Provider提供的服务
- Registry返回服务地址给Consumer
- Consumer根据Registry提供的服务地址调用Provider提供的服务
- Consumer和Provider定时向监控中心Monitor发送一些统计数据
影响版本
2.7.0 <= Dubbo Version <= 2.7.6
2.6.0 <= Dubbo Version <= 2.6.7
Dubbo 所有 2.5.x 版本(官方团队目前已不支持)
环境搭建
- zookeeper环境搭建:https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz
下载完成后将conf下的zoo_sample.cfg
配置文件重命名为zoo.cfg
,并配置dataDir
和dataLogDir
路径
1 | dataDir=Y:\YouPath\apache-zookeeper-3.6.3-bin\data |
启动:zkServer.cmd,默认端口为2181
- 代码部分:直接使用枫师傅的demo:https://github.com/Claradoll/Security_Learning
api:
1 | package com.api; |
provider:
Springboot中使用了@EnableDubboConfig
注解
1 | package com.example.dubboprovider; |
@EnableDubboConfig
的作用是:
- 开启 Dubbo 配置功能:允许 Spring 自动扫描和加载与 Dubbo 相关的配置类(例如,
@DubboService
或@DubboReference
标注的内容)。- 启用配置中心:它可以连接到 Dubbo 的配置中心,例如 Zookeeper、Nacos 等,从而动态加载配置。
- 注册配置 Bean:将配置类(如
DubboConfig
的实现类)注册到 Spring 的上下文中。
然后是路由controller
1 | package com.example.dubboprovider.service; |
consumer
Springboot中使用了@EnableDubboConfig
注解
1 | package com.example.dubboconsumer; |
在controller中调用zookeeper的服务
1 | package com.example.dubboconsumer.consumer; |
漏洞调试
获取构造好的恶意Hashmap
这里选择使用Rome打JdbcRowSetImpl的getter完成JNDI注入
1 | public static Object getPayload() throws Exception { |
在calc路由下打个断点,然后访问
注意此处的iHello是个proxy class对象
获取恶意的hashmap之后进入IObject方法,开始invoke调用
首先获取方法名和参数,然后声明创建RPC连接对象和serviceKey,并setTargetServiceUniqueName
进入MockClusterInvoker的invoke,首先检查mock参数
在 Dubbo 中,
mock
是一个配置参数,用于表示服务调用是否启用“模拟逻辑”或“服务降级”功能。它的核心作用是在服务调用失败或不可用的情况下,提供一个替代逻辑来避免调用失败的影响。
此处为false,然后进入替代逻辑,还是一个反射调用
进入AbstractCluster的invoke,这里有个拦截器的调用
然后一路跟进到decode,这里在处理着二进制数据的加解密
经过handleValue#RecodeableRpcResult
,后先入经过hessian2和hessian的readObject
然后恢复map的时候要put,调用了Hashmap的hash函数,从而打了rome链子
在rome链子的toString中打getter,从而JNDI注入
完整堆栈:
1 | connect:624, JdbcRowSetImpl (com.sun.rowset) |
Apache Dubbo Hessian2异常处理反序列化漏洞(CVE-2021-43297)
参考TCTF2022 Hessian-onlyJdk
Hessian先到此结束吧。。。
参考
- https://goodapple.top/archives/1193
- https://boogipop.com/2023/03/21/%E8%A2%AB%E6%88%91%E5%BF%98%E6%8E%89%E7%9A%84Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#Apache-Dubbo-Hessian%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-CVE-2020-1948
- https://xz.aliyun.com/t/13599?time__1311=GqmxuDc7iQKiqGNDQ0PBKqKhCGCSddYa4D#toc-10