GeoServer Stored Cross-Site Scripting (XSS) vulnerability in MapML HTML Page
Description
GeoServer is an open source software server written in Java that allows users to share and edit geospatial data. A stored cross-site scripting (XSS) vulnerability exists in versions prior to 2.23.4 and 2.24.1 that enables an authenticated administrator with workspace-level privileges to store a JavaScript payload in the GeoServer catalog that will execute in the context of another user's browser when viewed in the MapML HTML Page. The MapML extension must be installed and access to the MapML HTML Page is available to all users although data security may limit users' ability to trigger the XSS. Versions 2.23.4 and 2.24.1 contain a patch for this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.geoserver.extension:gs-mapmlMaven | < 2.23.4 | 2.23.4 |
org.geoserver.extension:gs-mapmlMaven | >= 2.24.0, < 2.24.1 | 2.24.1 |
Affected products
1Patches
26f04adbdc6c2[GEOS-11154] Improve handling special characters in the MapML HTML Page
2 files changed · +36 −5
src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLController.java+5 −5 modified@@ -5,6 +5,7 @@ package org.geoserver.mapml; +import static org.apache.commons.text.StringEscapeUtils.escapeHtml4; import static org.geoserver.mapml.MapMLConstants.MAPML_MIME_TYPE; import java.io.IOException; @@ -186,13 +187,12 @@ public String Html( "/mapml/viewer/widget/mapml-viewer.js", null, URLMangler.URLType.RESOURCE); - String title = layerLabel; StringBuilder sb = new StringBuilder(); sb.append("<!DOCTYPE html>\n") .append("<html>\n") .append("<head>\n") .append("<title>") - .append(title) + .append(escapeHtml4(layerLabel)) .append("</title>\n") .append("<meta charset='utf-8'>\n") .append("<script type=\"module\" src=\"") @@ -225,17 +225,17 @@ public String Html( .append(longitude) .append("\" controls controlslist=\"geolocation\">\n") .append("<layer- label=\"") - .append(layerLabel) + .append(escapeHtml4(layerLabel)) .append("\" ") .append("src=\"") .append(request.getContextPath()) .append(request.getServletPath()) .append("/") - .append(layer) + .append(escapeHtml4(layer)) .append("/") .append(proj) .append("/") - .append(!styleName.isEmpty() ? "?style=" + styleName : "") + .append(!styleName.isEmpty() ? "?style=" + escapeHtml4(styleName) : "") .append("\" checked></layer->\n") .append("</mapml-viewer>\n") .append("</body>\n")
src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLControllerTest.java+31 −0 modified@@ -6,6 +6,9 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; import static org.geowebcache.grid.GridSubsetFactory.createGridSubSet; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -243,6 +246,34 @@ public void testHTML() throws Exception { "layerGroup".equalsIgnoreCase(d.title())); } + @Test + public void testEscaping() throws Exception { + String unescapedTitle = "title\"><"; + String escapedTitle = "title"><"; + Catalog catalog = getCatalog(); + LayerGroupInfo lg = catalog.getLayerGroupByName("layerGroup"); + MockHttpServletRequest request = createRequest("mapml/" + lg.getName() + "/osmtile"); + MockHttpServletResponse response = new MockHttpServletResponse(); + try { + lg.setTitle(unescapedTitle); + catalog.save(lg); + String htmlResponse = + mc.Html( + request, + response, + lg.getName(), + "osmtile", + Optional.empty(), + Optional.empty(), + Optional.empty()); + assertThat(htmlResponse, not(containsString(unescapedTitle))); + assertThat(htmlResponse, containsString(escapedTitle)); + } finally { + lg.setTitle(null); + catalog.save(lg); + } + } + @Test public void testNonExistentLayer() throws Exception { MockHttpServletRequest request = createRequest("mapml/" + "foo" + "/osmtile");
df65ff05250c[GEOS-11154] Improve handling special characters in the MapML HTML Page
2 files changed · +36 −5
src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLController.java+5 −5 modified@@ -5,6 +5,7 @@ package org.geoserver.mapml; +import static org.apache.commons.text.StringEscapeUtils.escapeHtml4; import static org.geoserver.mapml.MapMLConstants.MAPML_MIME_TYPE; import java.io.IOException; @@ -186,13 +187,12 @@ public String Html( "/mapml/viewer/widget/mapml-viewer.js", null, URLMangler.URLType.RESOURCE); - String title = layerLabel; StringBuilder sb = new StringBuilder(); sb.append("<!DOCTYPE html>\n") .append("<html>\n") .append("<head>\n") .append("<title>") - .append(title) + .append(escapeHtml4(layerLabel)) .append("</title>\n") .append("<meta charset='utf-8'>\n") .append("<script type=\"module\" src=\"") @@ -225,17 +225,17 @@ public String Html( .append(longitude) .append("\" controls controlslist=\"geolocation\">\n") .append("<layer- label=\"") - .append(layerLabel) + .append(escapeHtml4(layerLabel)) .append("\" ") .append("src=\"") .append(request.getContextPath()) .append(request.getServletPath()) .append("/") - .append(layer) + .append(escapeHtml4(layer)) .append("/") .append(proj) .append("/") - .append(!styleName.isEmpty() ? "?style=" + styleName : "") + .append(!styleName.isEmpty() ? "?style=" + escapeHtml4(styleName) : "") .append("\" checked></layer->\n") .append("</mapml-viewer>\n") .append("</body>\n")
src/extension/mapml/src/test/java/org/geoserver/mapml/MapMLControllerTest.java+31 −0 modified@@ -6,6 +6,9 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; import static org.geowebcache.grid.GridSubsetFactory.createGridSubSet; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -243,6 +246,34 @@ public void testHTML() throws Exception { "layerGroup".equalsIgnoreCase(d.title())); } + @Test + public void testEscaping() throws Exception { + String unescapedTitle = "title\"><"; + String escapedTitle = "title"><"; + Catalog catalog = getCatalog(); + LayerGroupInfo lg = catalog.getLayerGroupByName("layerGroup"); + MockHttpServletRequest request = createRequest("mapml/" + lg.getName() + "/osmtile"); + MockHttpServletResponse response = new MockHttpServletResponse(); + try { + lg.setTitle(unescapedTitle); + catalog.save(lg); + String htmlResponse = + mc.Html( + request, + response, + lg.getName(), + "osmtile", + Optional.empty(), + Optional.empty(), + Optional.empty()); + assertThat(htmlResponse, not(containsString(unescapedTitle))); + assertThat(htmlResponse, containsString(escapedTitle)); + } finally { + lg.setTitle(null); + catalog.save(lg); + } + } + @Test public void testNonExistentLayer() throws Exception { MockHttpServletRequest request = createRequest("mapml/" + "foo" + "/osmtile/");
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-7x76-57fr-m5r5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-23819ghsaADVISORY
- github.com/geoserver/geoserver/commit/6f04adbdc6c289f5cb815b1462a6bd790e3fb6efghsax_refsource_MISCWEB
- github.com/geoserver/geoserver/commit/df65ff05250cbb498c78af906d66e0c084ace8a1ghsax_refsource_MISCWEB
- github.com/geoserver/geoserver/pull/7175ghsax_refsource_MISCWEB
- github.com/geoserver/geoserver/security/advisories/GHSA-7x76-57fr-m5r5ghsax_refsource_CONFIRMWEB
- osgeo-org.atlassian.net/browse/GEOS-11154ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.