FlawOpen

flawopen.com/Teardowns/cve-2026-63277-libreoffice-calc-jdbc-classpath-rce

● CVE-2026-63277 · CVSS v4 8.5 · 高危
安全研究 · FlawOpen

CVE-2026-63277:打开 Calc 表格时的 Java 代码执行

经过构造的 Calc 文档可利用外部数据映射,使 LibreOffice 在有人打开表格时从非本地 Java class path 解析 JDBC 驱动类。LibreOffice 26.2.5 和 26.8.0 限制此类条目只能使用 file scheme。必须有人打开文档,因此这不是零交互网络漏洞。[1], [2], [3]

💡 通俗易懂的原理解析 (ELI5)

电子表格可以记录程序应从哪里查找数据库驱动。修复前,这个位置可以指向远程地址,用户打开文件时程序可能从那里加载 Java 代码。修复只接受带有 file: scheme 的地址。这缩小了路径范围,但这个标签本身不能证明每个目标都在物理本地磁盘上。[1], [4], [5]

核心概念与专有名词

外部数据映射
Calc 文档中的设置,用于描述工作表如何连接外部数据源。[1]
JDBC 驱动
让应用程序能够与数据库通信的 Java 软件。[1]
Class path
Java 可以从中加载类及相关资源的一组位置。[4], [5]
CWE-829
从不可信控制域引入功能;在此路径中,class path URL 超出了预期的本地文件 scheme。[6], [1]

框架映射

  • 本指南所述弱点的 CWE 参考: CWE-829

根本原因剖析 (Root Cause)

存在漏洞的 jvmaccess::ClassPath::translateToUrls 路径会展开每个 Java class path 条目,并在未先限制 scheme 的情况下将其交给 Java URL 构造器。26.2.5.2 源码加入 URI 解析,并在构造 Java URL 前拒绝 scheme 不是 file 的条目。厂商公告将该路径与 Calc 外部数据映射及 JDBC 驱动加载关联起来。需要有人打开经过构造的文档才能触发该路径;所引用资料没有证明零交互可达或存在实战利用。保护措施检查的是 scheme:不要把它说成通用授权策略,也不要声称它拒绝所有远程文件共享。[1], [4], [5]

CVE-2026-63277:Calc 触发 Java 加载的条件

检查项受影响条件或已确认行为防御措施
由文档控制的输入文档的外部数据映射可以指定 JDBC 驱动使用的 class path。将不可信来源的文档和链接视为不可信输入;升级到修复版本。
用户交互用户必须打开文档,才能进入厂商描述的加载路径。不要称其为零交互;审查文档接收与打开流程。
修复边界Scheme 检查不能证明每个 file: 目标都位于物理本地磁盘,也不能证明网络共享会被拒绝。将改动描述为 scheme 校验;不要声称它提供更广泛的授权或本地性保证。

攻击执行流程分解

步骤 1

文档包含外部数据映射

受影响文档描述外部数据源以及 Java 数据库驱动类的 class path。[1], [4]

步骤 2

有人打开文档

需要用户交互;打开任意电子表格不等同于远程零交互利用。[1], [2]

步骤 3

Java class path 被转换为 URL

修复前,translateToUrls 会把展开后的条目交给 Java URL 构造器,却不检查 scheme 是否为本地文件。[4]

步骤 4

引用的驱动可能被加载

厂商公告描述了该 Java 加载路径。影响取决于文档、配置和 Java 集成;不能推断每个 Calc 文档或安装都必然可被利用。[1], [2]

步骤 5

修复版本拒绝其他 scheme

LibreOffice 26.2.5.2 会解析 URI;除非 scheme 是 file,否则在创建 URL 前抛出异常。请安装厂商或发行版提供的修复版本。[5], [1]

源代码对比:漏洞与安全实现

这些精简代码片段来自指定版本的上游 translateToUrls 实现。26.2.4.2 会展开条目,然后直接创建 Java URL;26.2.5.2 加入 URI 解析,并在创建 URL 前拒绝 file 以外的 scheme。源码差异证明了 scheme 校验;厂商公告确认 Calc/JDBC 文档路径。此处是源码分析,不是可运行的利用代码。[4], [5], [1]

源码摘录: jvmaccess/source/classpath.cxx — translateToUrls 函数

存在漏洞的实现
// LibreOffice 26.2.4.2 — jvmaccess/source/classpath.cxx(存在漏洞的版本)
if (!url.isEmpty()) {
    css::uno::Reference< css::uri::XVndSunStarExpandUrlReference >
        expUrl(
            css::uri::UriReferenceFactory::create(context)->parse(url),
            css::uno::UNO_QUERY);
    if (expUrl.is()) {
        css::uno::Reference< css::util::XMacroExpander > expander =
            css::util::theMacroExpander::get(context);
        try {
            url = expUrl->expand( expander );
        } catch (const css::lang::IllegalArgumentException & e) {
            css::uno::Any anyEx = cppu::getCaughtException();
            throw css::lang::WrappedTargetRuntimeException(
                "com.sun.star.lang.IllegalArgumentException: "
                 + e.Message,
                 nullptr, anyEx );
        }
    }
    jvalue arg;
    arg.l = environment->NewString(
        reinterpret_cast< jchar const * >(url.getStr()),
        static_cast< jsize >(url.getLength()));
    if (arg.l == nullptr) {
        return nullptr;
    }
    jobject o(environment->NewObjectA(classUrl, ctorUrl, &arg));
    if (o == nullptr) {
        return nullptr;
    }
    urls.push_back(o);
}
加固后的安全修复
// LibreOffice 26.2.5.2 — jvmaccess/source/classpath.cxx(修复后的版本)
if (!url.isEmpty()) {
    css::uno::Reference< css::uri::XVndSunStarExpandUrlReference >
        expUrl(
            css::uri::UriReferenceFactory::create(context)->parse(url),
            css::uno::UNO_QUERY);
    if (expUrl.is()) {
        css::uno::Reference< css::util::XMacroExpander > expander =
            css::util::theMacroExpander::get(context);
        try {
            url = expUrl->expand( expander );
        } catch (const css::lang::IllegalArgumentException & e) {
            css::uno::Any anyEx = cppu::getCaughtException();
            throw css::lang::WrappedTargetRuntimeException(
                "com.sun.star.lang.IllegalArgumentException: "
                 + e.Message,
                 nullptr, anyEx );
        }
    }
    css::uno::Reference< css::uri::XUriReference > uriRef(
        css::uri::UriReferenceFactory::create(context)->parse(url));
    if (!uriRef.is() || !uriRef->getScheme().equalsIgnoreAsciiCase("file"))
    {
        throw css::lang::IllegalArgumentException(
            "non-local Java class path entry: " + url,
            css::uno::Reference< css::uno::XInterface >(), 0);
    }
    jvalue arg;
    arg.l = environment->NewString(
        reinterpret_cast< jchar const * >(url.getStr()),
        static_cast< jsize >(url.getLength()));
    if (arg.l == nullptr) {
        return nullptr;
    }
    jobject o(environment->NewObjectA(classUrl, ctorUrl, &arg));
    if (o == nullptr) {
        return nullptr;
    }
    urls.push_back(o);
}

工程与系统安全加固清单

参考来源