POJONode#toString调用getter方法原理
前言
前面说到了在fastjson中的原生的一个反序列化调用任意类的getter方法的原理与细节
从最开始的fastjson < 1.2.48下的在JSONObject / JSONArray
类反序列化过程没有安全检查的情况下通过BadAttributeValueExpException#readObject
调用JSONObject#toString / JSONArray#toString
方法也即是JSON#toString
方法触发getter
再到fastjson >= 1.2.48下的存在有SecureObjectInputStream
的安全检查的情况下,通过使用HashMap
等等类创建一个reference
的方式绕过resolveClass的检查触发getter
既然在fastjson中存在有这样的原生反序列化,在另一个和他功能类似的开源库jackson也有着类似的原生反序列化触发getter方法
Jackson的知识点
简单的认识一下Jackson的序列化触发getter的流程
在jackson中将对象序列化成一个json串主要是使用的ObjectMapper#writeValueAsString
方法
使用这个方法的过程中将会调用getter方法
大致的调用栈如下
1 | serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)_writeValueAndClose:4568, ObjectMapper (com.fasterxml.jackson.databind)writeValueAsString:3821, ObjectMapper (com.fasterxml.jackson.databind) |
提及几个关键的方法
1 | DefaultSerializerProvider#serializeValue: |
通过findTypedValueSerializer
来从缓存中获取对应的序列化器,如果没有将会创建一个序列化器并写入缓存中,这里传入的是一个POJO对象,所以得到的是一个BeanSerializer
类 来到BeanSerializer#serialize
进行json串的构造
首先是调用writeStartObject
方法写入{
字符,之后中间是对Bean对象的属性值的一些构造,最后是调用writeEndObject
方法写入}
字符
而在BeanSerializerBase#serializeFields
方法中
而最后是能够调用对应属性值的getter方法进行赋值
例题
这里同样是通过一道CTF的题目来引入jackson中的原生反序列化
就直接是获取了请求体的序列化数据进行反序列化,没有任何的过滤
而在pom.xml
中查看依赖,除了存在有spring-boot-starter-web:2.6.11
之外没有任何依赖
而这里的关键点jackson相较于fastjson的优势就是与springboot紧密结合,所以jackson这个依赖是存在这个spring-boot-starter-web
依赖中的
根据前面的知识点,我们需要构造BadAttributeValueExpException#readObject -> POJONode#toString -> getter
的链子
至于为什么要选择使用POJONode
类来进行getter方法的触发在后面将会详细的描述
POC:
C
1 | lassPool pool = ClassPool.getDefault(); |
这里有点小坑,如果直接执行上面的POC,在序列化过程中将会出现错误:
根据报错定位到ObjectOuptputStream#writeObject0
中
如果序列化的类实现了writeReplace
方法,将会在序列化过程中调用它进行检查,好巧不巧,在POJONode
的父类BaseJsonNode
中就实现了这个方法,在这个方法的调用过程中抛出了异常,使得序列化过程中断
我们可以通过删除这个方法来跳过这个过程,进而成功的序列化
之后POC就能打通,后面的过程就很简单了
getter方法的调用
在前面的过程中,留下了一个问题,也就是为什么POJONode类的toString方法能够调用对应类对象的getter方法呢?
POJONode中不存在有toString方法的实现,在其父类BaseJsonNode
中存在有,因其为一个抽象类,所以选择使用POJONode这个没有实现toString方法的类进行利用
依次调用了toString -> InternalNodeMapper#nodeToString -> ObjectWriter.writeValueAsString
方法
最关键的就是最后一个方法的调用了,在最前的Jackson
的知识点中,提到了在将一个Bean对象序列化一个json串的使用常用的方法是writeValueAsString
方法,并详细分析了,在调用该方法的过程中将会通过遍历的方法将bean对象中的所有的属性的getter方法进行调用
这里也是getter方法调用的成因
Other Gadgets
除了使用POJONode类的方式还存在其他的利用链吗?如果BadAttributeValueExpException
等可以触发toString
方法的类被Ban了,还有会利用的可能型吗?谈谈第一个问题:
按照理论来说只需要寻找到继承BaseJsonNode
的类,并且没有重写toSting方法,就能够替代POJONode
类
几乎是与数据类型相关的,除了POJONode类我没有发现可以存放Object并序列化的类
有或者是找到在jackson中存在有writeValueAsString
方法的调用的类