Kryo
0x01 What Is Kryo
Kryo 是一个快速序列化/反序列化工具,依赖于字节码生成机制(底层使用了 ASM 库),因此在序列化速度上有一定的优势,但正因如此,其使用也只能限制在基于 JVM 的语言上(Scala、Kotlin)
其他类似的序列化工具:原生JDK、Hessian、FTS
官方文档👉https://github.com/EsotericSoftware/kryo
0x02 Quick Start
引入依赖
待序列化目标类MyClass:
0x03 Ser and Deser
Kryo
提供了三组方法来读写对象
类未知且对象可能为null
类已知且对象可能为null
类已知且对象不为null
这些方法首先都是找到合适的序列化器(serializer),再进行序列化或反序列化,序列化器会递归地调用这些方法。
Kryo的注册
Kryo为了提供性能和减小序列化结果体积,提供注册序列化对象类的方式。
在注册时,会为该序列化类生成int ID, 后续在序列化时使用int ID唯一标识该类型
序列化流程
跟进writeClassAndObject
Registration获取
writeClass(output, object.getClass())
返回一个object
类的Registration
若该类没有注册过(也就是没有上面的kryo.register
指定一个类),会自动使用默认的序列化器注册,注册有两个目的:获取序列化器和类的唯一标识Id,方便后续的序列化和反序列化
com.esotericsoftware.kryo.util.ObjectMap
类维护了一个Class
与Registration
(含相对应的反序列化器)的对应表
当然我们自定义的类肯定在这个表中找不到,里面都是Java的基础类,DefaultClassResolver#getRegistration
就返回null
接着进入registerImplicit
-> getDefaultSerializer
继续找一些Java内置类是否和待序列化类对应
继续跟进,发现FieldSerializer
作为默认序列化器,并在FieldSerializer#rebuildCachedFields
中获取序列化类的Fields
,忽略静态成员
到此就获取到了自定义类的Registration
Field序列化
接着进入FieldSerializer.write(this, output, object);
Kryo
封装了一个UnsafeUtil
(Unsafe
对象通过反射获取)
在JVM中,对实例的Field进行了有规律的存储,通过一个偏移量可以从内存中找到相应的Field值
unsafe实现了在内存层面,通过成员字段偏移量offset来获取对象的属性值
接着获取成员的序列化器,步骤跟上面的一样(getRegistration(type).getSerializer()
)
剩下的就是继续递归所有成员,获取序列化器进行序列化
反序列化流程
同样也是先获取类的Registration
,再从Registration
拿序列化器器
FieldSerializer#read
首先对类进行实例化,这里是使用了Kryo封装的com.esotericsoftware.reflectasm#ConstructorAccess
去构造类对象,基于ASM,还没学过ASM,就不深入跟进去看了
同样是获取成员的序列化器,递归调用readObject
可以跟一下这里的readObjectOrNull
这里的序列化器是StringSerializer
,直接从输入流input读取了,否则就继续调用上面的FieldSerializer#read
了
后面的setField
也是用unsafe
从内存层面往成员偏移量处填充值
到此反序列化就结束了
总结
0x04 Way To Attack
网上找到的Kryo反序列化问题都是放在Dubbo那块的。Dubbo默认的序列化协议是Hessian,但可以修改Dubbo协议数据包中的header,指定SerializationID,来确定Consumer和Provider通信使用的序列化协议,这里就不细讲Dubbo数据包的修改了,而是抽取其中关键的Kryo反序列化,Dubbo相关的具体可以看这里
调用栈
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
write:-1, ASMSerializer_1_TemplatesImpl (com.alibaba.fastjson.serializer)
write:270, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:280, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:863, JSON (com.alibaba.fastjson)
toString:857, JSON (com.alibaba.fastjson)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
read:162, MapSerializer (com.esotericsoftware.kryo.serializers)
read:39, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:813, Kryo (com.esotericsoftware.kryo)
Kryo从input中读取解析到type为HashMap
因此会调用MapSerializer
序列化器来读取input中的信息
既然是Map的反序列化就肯定涉及到键值对的处理
MapSerializer
会将解析到的key和value都通过调用map.put()
来放入HashMap对象中
接着调用putVal()
,equals()
判断两个键是否相对
com.sun.org.apache.xpath.internal.objects.XString#equals
会调用toString
org.springframework.aop.target.HotSwappableTargetSource#equals
多套一个HotSwappableTargetSource
是为了让HashMap的putVal
能走到equals
这里触发com.alibaba.fastjson.JSON
类的toString()
函数,进而调用JSONSerializer
的write()
函数,从而触发Fastjson Gadget
0x05 Reference
Last updated