Moderate severityNVD Advisory· Published Nov 25, 2025· Updated Nov 26, 2025
GeoServer Reflected Cross-Site Scripting (XSS) vulnerability in WMS GetFeatureInfo HTML format
CVE-2025-21621
Description
GeoServer is an open source server that allows users to share and edit geospatial data. Prior to version 2.25.0, a reflected cross-site scripting (XSS) vulnerability exists in the WMS GetFeatureInfo HTML output format that enables a remote attacker to execute arbitrary JavaScript code in a victim's browser through specially crafted SLD_BODY parameters. This issue has been patched in version 2.25.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.geoserver.web:gs-web-appMaven | < 2.25.0 | 2.25.0 |
org.geoserver:gs-wmsMaven | < 2.25.0 | 2.25.0 |
Affected products
1Patches
1dc9ff1c726dd[GEOS-11297] Escape WMS GetFeatureInfo HTML output by default
6 files changed · +90 −15
doc/en/user/source/installation/upgrade.rst+12 −0 modified@@ -30,6 +30,18 @@ The general GeoServer upgrade process is as follows: Notes on upgrading specific versions ------------------------------------ +FreeMarker Template HTML Auto-escaping (GeoServer 2.25 and newer) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As of GeoServer 2.25, the FreeMarker library's HTML auto-escaping feature will be enabled by default to prevent +cross-site scripting (XSS) vulnerabilities in WMS GetFeatureInfo HTML output when using the default FreeMarker +templates and WMS service settings. Some users may experience incorrectly escaped HTML output when using custom +templates or if HTML tags are stored in vector data stores. + +See the :ref:`production_config_freemarker_escaping` page for information about the limitations of this feature +and for instructions to disable this feature and delegate to the WMS service setting which defaults to disabling +HTML auto-escaping. + Spring Security Strict HTTP Firewall (GeoServer 2.25 and newer) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
doc/en/user/source/production/config.rst+22 −0 modified@@ -188,6 +188,28 @@ In case you want it set to ``text/xml`` instead, you need to setup the Java Syst * ``-Dows10.exception.xml.responsetype=text/xml`` for OWS 1.0.0 version * ``-Dows11.exception.xml.responsetype=text/xml`` for OWS 1.1.0 version +.. _production_config_freemarker_escaping: + +FreeMarker Template Auto-escaping +--------------------------------- + +By default, FreeMarker's built-in automatic escaping functionality will be enabled to mitigate potential cross-site scripting +(XSS) vulnerabilities in cases where GeoServer uses FreeMarker templates to generate HTML output and administrators are able +to modify the templates and/or users have significant control over the output through service requests. When the +``GEOSERVER_FORCE_FREEMARKER_ESCAPING`` property is set to false, auto-escaping will delegate either to the feature's default +behavior or other settings which allow administrators to enable/disable auto-escaping on a global or per virtual service +basis. This property can be set to false either via Java system property, command line argument (-D), environment variable or +web.xml init parameter. + +This setting currently applies to the WMS GetFeatureInfo HTML output format and may be applied to other applicable GeoServer +functionality in the future. + +.. warning:: + While enabling auto-escaping will prevent XSS using the default templates and mitigate many cases where template authors + are not considering XSS in their template design, it does **NOT** completely prevent template authors from creating + templates that allow XSS (whether this is intentional or not). Additional functionality may be added in the future to + mitigate those potential XSS vulnerabilities. + .. _production_config_external_entities: External Entities Resolution
doc/en/user/source/services/wms/webadmin.rst+2 −0 modified@@ -223,6 +223,8 @@ By default GetFeatureInfo results are printed in the HTML templates without any When the flag is checked, values that are printed in the HTML templates for GetFeatureInfo requests results will be automatically escaped. The default FreeMarker templates can be overridden to enable or disable auto-escaping on a per template, per block or per value basis. +.. note:: Auto-escaping is forced to be enabled by default and that property must be disabled for this setting to have any effect. See the :ref:`production_config_freemarker_escaping` page for instructions. + Setting Remote Style max connection and request time ----------------------------------------------------
doc/en/user/source/tutorials/GetFeatureInfo/html.rst+1 −1 modified@@ -164,7 +164,7 @@ The ``value`` property of Feature attribute values are given by geoserver in ``S Auto-Escaping ````````````` -Auto-escaping can be used to escape special characters so that they are displayed correctly in clients and to prevent injection. GeoServer administrators can enable or disable auto-escaping for FreeMarker template values for the HTML output format on a global or per virtual service basis. Template authors are able to override the WMS service setting to enable or disable escaping on a per template, per block or per value basis. See `Auto-escaping <https://freemarker.apache.org/docs/dgui_misc_autoescaping.html>` for more information. +Auto-escaping can be used to escape special characters so that they are displayed correctly in clients and to prevent injection. GeoServer administrators can enable or disable auto-escaping for FreeMarker template values for the HTML output format on a global or per virtual service basis. Template authors are able to override the WMS service setting to enable or disable escaping on a per template, per block or per value basis. See `Auto-escaping <https://freemarker.apache.org/docs/dgui_misc_autoescaping.html>`_ for more information. Accessing static methods ````````````````````````
src/wms/src/main/java/org/geoserver/wms/featureinfo/FreeMarkerTemplateManager.java+12 −2 modified@@ -47,6 +47,13 @@ */ public abstract class FreeMarkerTemplateManager { + /** + * System property to control whether or not to enable FreeMarker's auto-escaping of HTML + * output. This property will override the WMS setting to enable/disable auto-escaping. Default + * is true. + */ + public static final String FORCE_FREEMARKER_ESCAPING = "GEOSERVER_FORCE_FREEMARKER_ESCAPING"; + /** Config key determining the restrictions for accessing static members */ static final String KEY_STATIC_MEMBER_ACCESS = "org.geoserver.htmlTemplates.staticMemberAccess"; @@ -275,8 +282,11 @@ private Template getTemplate(ResourceInfo ri, String templateFileName, Charset c templateLoader.setResource(ri); templateConfig.setTemplateLoader(templateLoader); templateConfig.unsetOutputFormat(); - if (format.equals(OutputFormat.HTML) && wms.isAutoEscapeTemplateValues()) { - templateConfig.setOutputFormat(HTMLOutputFormat.INSTANCE); + if (format.equals(OutputFormat.HTML)) { + String prop = GeoServerExtensions.getProperty(FORCE_FREEMARKER_ESCAPING); + if (!"false".equalsIgnoreCase(prop) || wms.isAutoEscapeTemplateValues()) { + templateConfig.setOutputFormat(HTMLOutputFormat.INSTANCE); + } } Template t = null; try {
src/wms/src/test/java/org/geoserver/wms/featureinfo/HTMLFeatureInfoOutputFormatTest.java+41 −12 modified@@ -140,6 +140,11 @@ public Object findTemplateSource(String path) throws IOException { getFeatureInfoRequest.setQueryLayers(queryLayers); } + @After + public void resetSetting() { + System.clearProperty(FreeMarkerTemplateManager.FORCE_FREEMARKER_ESCAPING); + } + @SuppressWarnings("unchecked") // EMF model without generics private void initFeatureType(FeatureTypeInfo featureType) throws IOException { fcType = WfsFactory.eINSTANCE.createFeatureCollectionType(); @@ -424,29 +429,53 @@ public void testAutoEscaping() throws Exception { resource.getKeywords().set(0, new Keyword(decoded)); getCatalog().save(resource); - // test with auto-escaping disabled - ByteArrayOutputStream outStream1 = new ByteArrayOutputStream(); - outputFormat.write(fcType, getFeatureInfoRequest, outStream1); - String result1 = new String(outStream1.toByteArray()); - assertThat(result1, containsString(decoded)); - assertThat(result1, not(containsString(encoded))); + // test with no system property defined (default is true) and WMS setting disabled + // result will be escaped + doTestAutoEscaping(null, encoded, decoded); + + // test with system property set to true and WMS setting disabled + // result will be escaped + doTestAutoEscaping("true", encoded, decoded); + + // test with system property set to false and WMS setting disabled + // result will not be escaped + doTestAutoEscaping("false", decoded, encoded); - // test with auto-escaping enabled WMSInfo info = getGeoServer().getService(WMSInfo.class); info.setAutoEscapeTemplateValues(true); getGeoServer().save(info); try { - ByteArrayOutputStream outStream2 = new ByteArrayOutputStream(); - outputFormat.write(fcType, getFeatureInfoRequest, outStream2); - String result2 = new String(outStream2.toByteArray()); - assertThat(result2, not(containsString(decoded))); - assertThat(result2, containsString(encoded)); + // test with no system property defined (default is true) and WMS setting enabled + // result will be escaped + doTestAutoEscaping(null, encoded, decoded); + + // test with system property set to true and WMS setting enabled + // result will be escaped + doTestAutoEscaping("true", encoded, decoded); + + // test with system property set to false and WMS setting enabled + // result will be escaped + doTestAutoEscaping("false", encoded, decoded); } finally { info.setAutoEscapeTemplateValues(false); getGeoServer().save(info); } } + private void doTestAutoEscaping(String property, String contains, String notContains) + throws Exception { + if (property != null) { + System.setProperty(FreeMarkerTemplateManager.FORCE_FREEMARKER_ESCAPING, property); + } else { + System.clearProperty(FreeMarkerTemplateManager.FORCE_FREEMARKER_ESCAPING); + } + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + outputFormat.write(fcType, getFeatureInfoRequest, outStream); + String result = new String(outStream.toByteArray()); + assertThat(result, not(containsString(notContains))); + assertThat(result, containsString(contains)); + } + /** Restore FreeMarkerTemplateManager default state */ @After @Before
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-w66h-j855-qr72ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-21621ghsaADVISORY
- github.com/geoserver/geoserver/commit/dc9ff1c726dd73c884437a123b4ad72b19383c7dghsax_refsource_MISCWEB
- github.com/geoserver/geoserver/pull/7406ghsax_refsource_MISCWEB
- github.com/geoserver/geoserver/security/advisories/GHSA-w66h-j855-qr72ghsax_refsource_CONFIRMWEB
- osgeo-org.atlassian.net/browse/GEOS-11203ghsaWEB
- osgeo-org.atlassian.net/browse/GEOS-11297ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.