Geospatial metadata catalogs are one of those pieces of infrastructure that nobody talks about and quietly sit behind government open-data portals, environmental agencies, and enterprise GIS platforms, cataloging where things are and what is known about them.
GeoNetwork is a widely used open-source geospatial metadata catalog and an OSGeo project. Originally developed at the UN's FAO, it has become a core component in many Spatial Data Infrastructure initiatives across Europe and beyond. If you've browsed a national or regional geoportal, there's a real chance GeoNetwork was helping serve the metadata underneath.
Being used by government entities it was what interested me, so that interested turned into four separate findings :
CVE-2026-63219 - Unauthenticated file upload via missing Authorization on formatter Upload Endpoint
CVE-2026-55864 - Unauthenticated Server-Side Request Forgery in SLD Tool
CVE-2026-57582 - Reflected XSS via unsanitized Javascript Sink
CVE-2026-58400 - Remote Code Execution via unsafe Saxon XSLT processor configuration in formatter
Let's get started!
How GeoNetwork works
Before we start, a quick tour of the bits that matter for this post.
GeoNetwork is a Java / Spring web application. Under the hood, three concepts are going to keep coming back:
- Records Every piece of metadata is a record, identified by a UUID. Plenty of these are public by design and you read them anonymously. In any real-world instance, it is basically certain there's at least one public record lying around.
- The /srv API - Almost everything is exposed under a portal-scoped API, like GET /srv/api/records/{uuid}. You'll see this prefix everywhere.
- XSLT, everywhere. GeoNetwork renders and transforms metadata using XSLT stylesheets. The page layouts are XSLT. The “formatters” that turn a record into an HTML or text view are XSLT. This detail is going to matter soon.
On the security side, role operations are guarded with Spring's @PreAuthorize annotation such as:
The interesting question in this situation is always “what endpoint was forgotten?”
GeoVulnerabilities
Unauthenticated file upload via missing Authorization on formatter Upload Endpoint
Admin endpoints are supposed to be guarded by @PreAuthorize.
The controller that manages formatters is FormatterAdminApi. As the name tells, it's admin functionality, meaning it has functionalities like listing, downloading, deleting, updating formatter files and every single one of those operations is locked behind:
Which is good, except the method that writes a brand-new formatter to disk:
One line missing and now anonymous users can use the addFormatter method to upload their own formatters into the server. Funny enough, the endpoint was secure in GeoNetwork instances bellow 4.0.6 version, but with the re-facture of the endpoint on version 4.0.6, the PreAuthorize line was forgotten.
Remote Code Execution via unsafe Saxon XSLT
Now, uploading an XSLT stylesheet is only interesting if that stylesheet later gets executed, and that’s where the Saxon-B library engine comes in. When the library processes the XSLT file into a XML file to then be rendered on the website, It processes the file carelessly enough to let a stylesheet do dangerous things. Let's check the engine.
The engine is configured at common/src/main/java/org/fao/geonet/utils/Xml.java
The ALLOW_EXTERNAL_FUNCTIONS is never set. Checking the documentation, it says :
The default value is true. The setting false is recommended in an environment where untrusted stylesheets may be executed.
That means a stylesheet is allowed to call directly Java Runtime, thus, allowing us to achieve remote code execution by uploading the following malicious XSLT:
After uploading, we just need to trigger the formatter by visiting the record with a simple GET:
We control both values. We know our formatter name, because we just uploaded it. And a public record UUID is trivially discoverable because GeoNetwork's own search API hands it over So the whole chain looks like this:
The full attack chain: an unauthenticated POST uploads a malicious XSLT formatter, a follow-up GET triggers Saxon-B to execute itCollisions
Sadly, my submission for this vulnerability ended up being a collision with another researcher. We ended up contacting each other and it was decided that we would be happy to share the credits of the vulnerability, as we both found the issue independently.
So shout out to Brexard for finding this vulnerability, great researcher.
Unauthenticated SSRF
GeoNetwork ships an SLD (”Styled Layer Descriptor”) tool for building map styles. It lives at POST /api/tools/ogc/sld, and it takes a caller-supplied WMS server URL :
That serverURL goes straight into SLDUtil.parseSLD(new URI(serverURL), ...) with no allowlist, no scheme check, no SSRF guard. And what does parseSLD do with it?
It fires a server-side HTTP GET at whatever we asked for. That's a straight forward SSRF, an anonymous attacker now can make requests inside the server's network, able to reach internal hosts and services that were never meant to be exposed.
We can trigger the SSRF with the following HTTP request :
Blind or not Blind ?
Looking back at buildSLD, the fetched body is parsed with Xml.loadString(...) and flows back into the response. This of course is a limitation because we need an internal resource that returns XML, for example, configurations or sensitive data stored in some endpoint.
Reflected XSS via unsanitized Javascript Sink
The catalog.search route takes a uiconfig query parameter. It's supposed to carry UI configuration. Inside base-layout-cssjs-loader.xsl, the parameter is read straight into a variable:
Following that variable a few lines down and it gets emitted into an inline <script> block as the first argument to a function call:
Then getting our lovely XSS is trivial, gnGlobalSettings.init(...) still needs a valid first argument, or the rest of the script breaks. That can be solved with uiconfig=(alert(),{}) , which then renders as:
(alert(),{}) runs alert() and then evaluates to {} , so our code fires and init() still receives the empty object it wanted. Everyone's happy. Or we could just close the <script> tag and get XSS with some HTML code, but where the fun in that :) .
URL-encoded, the full payload is just a link:
GET /srv/eng/catalog.search?uiconfig=%28alert%281%29%2C%7B%7D%29
XSS confirmed: the crafted URL triggers an alert in the victim's browser, with no authentication required.According to the GeoNetwork Team, this is a high severity issue, the explanation stated is bellow:
The severity listed is Medium, but we'd bump this to High. The endpoint requires no authentication, the XSRF-TOKEN cookie is readable from JavaScript (not HttpOnly), and combining those two facts makes full account takeover possible in one step with no user interaction beyond clicking a link.
Chaining Unauthorised Upload and unsafe XSLT processing
As you may expect, we can chain the Unauthorised Upload vulnerability with the unsafe XSLT processing vulnerability and get unauthenticated Remote Code Execution. The following video demonstrates an unauthenticated user achieving a reverse shell against GeoNetwork 4.4.11 version.
Mass Fingerprinting and Reporting
Because mainly government and international entities use this software, after reporting the vulnerabilities to the GeoNetwork team, it was time to mass report the vulnerabilities as an unauthenticated RCE was achievable in instances ≥ 4.0.6.
Overall, this were the statistics we ended up with :
- 121 affected GeoNetwork 4.x deployments were identified across 39 countries/regions.
- 89% of affected instances were government, military, or national-agency related.
- 11% of affected instances belonged to academic, nonprofit-adjacent or commercial organizations.
- Europe and EU/international-facing deployments accounted for 77.7% of the dataset.
All these entities that were using GeoNetwork with the unauthenticated RCE affected versions were contacted with the vulnerability details and pre-patch mitigations to secure their assets while the GeoNetwork team was working on an official patch.
Conclusion
GeoNetwork was interesting because it sits in places where security mistakes matter: government portals, national geospatial catalogs, research infrastructure, and environmental platforms. In the end, this was a good reminder that quiet infrastructure is still critical infrastructure, and small authorization gaps in widely deployed software can quickly become a scaling problem.
With that said, thank you for reading the post, and hope you enjoyed it!
