VYPR
Moderate severityNVD Advisory· Published Mar 20, 2024· Updated Aug 1, 2024

GeoServer Stored Cross-Site Scripting (XSS) vulnerability in GWC Seed Form

CVE-2024-23643

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.2 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 administrator’s browser when viewed in the GWC Seed Form. Access to the GWC Seed Form is limited to full administrators by default and granting non-administrators access to this endpoint is not recommended. Versions 2.23.2 and 2.24.1 contain a fix for this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.geoserver:gs-gwc-restMaven
< 2.23.22.23.2
org.geoserver:gs-gwc-restMaven
>= 2.24.0, < 2.24.12.24.1

Affected products

1

Patches

2
c0ca08a20bc0

[GWC-1172] Improve handling special characters in the GWC Seed Form

https://github.com/GeoWebCache/geowebcacheSteve IkeokaOct 10, 2023via ghsa
2 files changed · +66 17
  • geowebcache/rest/src/main/java/org/geowebcache/rest/service/FormService.java+24 17 modified
    @@ -18,6 +18,7 @@
      */
     package org.geowebcache.rest.service;
     
    +import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;
     import static org.geowebcache.seed.TileBreeder.TILE_FAILURE_RETRY_COUNT_DEFAULT;
     import static org.geowebcache.seed.TileBreeder.TILE_FAILURE_RETRY_WAIT_TIME_DEFAULT;
     import static org.geowebcache.seed.TileBreeder.TOTAL_FAILURES_BEFORE_ABORTING_DEFAULT;
    @@ -356,7 +357,10 @@ private void makeModifiableParameters(StringBuilder doc, TileLayer tl) {
                 String key = pf.getKey();
                 String defaultValue = pf.getDefaultValue();
                 List<String> legalValues = pf.getLegalValues();
    -            doc.append("<tr><td>").append(key.toUpperCase()).append(": ").append("</td><td>");
    +            doc.append("<tr><td>")
    +                    .append(escapeHtml4(key.toUpperCase()))
    +                    .append(": ")
    +                    .append("</td><td>");
                 String parameterId = "parameter_" + key;
                 if (pf instanceof StringParameterFilter) {
                     Map<String, String> keysValues = makeParametersMap(defaultValue, legalValues);
    @@ -487,7 +491,7 @@ private void makeBboxHints(StringBuilder doc, TileLayer tl) {
                 GridSubset subset = tl.getGridSubset(gridSetId);
                 doc.append(
                         "<li>"
    -                            + gridSetId
    +                            + escapeHtml4(gridSetId)
                                 + ":   "
                                 + subset.getOriginalExtent().toString()
                                 + "</li>\n");
    @@ -501,11 +505,11 @@ private void makeTextInput(StringBuilder doc, String id, int size) {
         private void makeTextInput(StringBuilder doc, String id, int size, String defaultValue) {
             doc.append(
                     "<input name=\""
    -                        + id
    +                        + escapeHtml4(id)
                             + "\" type=\"text\" size=\""
                             + size
                             + "\" value=\""
    -                        + defaultValue
    +                        + escapeHtml4(defaultValue)
                             + "\"/>");
         }
     
    @@ -603,7 +607,7 @@ private void makeGridSetPulldown(StringBuilder doc, TileLayer tl) {
     
         private void makePullDown(
                 StringBuilder doc, String id, Map<String, String> keysValues, String defaultKey) {
    -        doc.append("<select name=\"" + id + "\">\n");
    +        doc.append("<select name=\"" + escapeHtml4(id) + "\">\n");
     
             Iterator<Map.Entry<String, String>> iter = keysValues.entrySet().iterator();
     
    @@ -612,16 +616,16 @@ private void makePullDown(
                 if (entry.getKey().equals(defaultKey)) {
                     doc.append(
                             "<option value=\""
    -                                + entry.getValue()
    +                                + escapeHtml4(entry.getValue())
                                     + "\" selected=\"selected\">"
    -                                + entry.getKey()
    +                                + escapeHtml4(entry.getKey())
                                     + "</option>\n");
                 } else {
                     doc.append(
                             "<option value=\""
    -                                + entry.getValue()
    +                                + escapeHtml4(entry.getValue())
                                     + "\">"
    -                                + entry.getKey()
    +                                + escapeHtml4(entry.getKey())
                                     + "</option>\n");
                 }
             }
    @@ -631,7 +635,10 @@ private void makePullDown(
     
         private void makeFormHeader(StringBuilder doc, TileLayer tl) {
             doc.append("<h4>Create a new task:</h4>\n");
    -        doc.append("<form id=\"seed\" action=\"./" + tl.getName() + "\" method=\"post\">\n");
    +        doc.append(
    +                "<form id=\"seed\" action=\"./"
    +                        + escapeHtml4(tl.getName())
    +                        + "\" method=\"post\">\n");
             doc.append("<table border=\"0\" cellspacing=\"10\">\n");
         }
     
    @@ -720,9 +727,9 @@ private void makeTaskList(StringBuilder doc, TileLayer tl, boolean listAll) {
                 doc.append("<td style=\"text-align:right\">").append(task.getTaskId()).append("</td>");
                 doc.append("<td>");
                 if (!layerName.equals(task.getLayerName())) {
    -                doc.append("<a href=\"./").append(task.getLayerName()).append("\">");
    +                doc.append("<a href=\"./").append(escapeHtml4(task.getLayerName())).append("\">");
                 }
    -            doc.append(task.getLayerName());
    +            doc.append(escapeHtml4(task.getLayerName()));
                 if (!layerName.equals(task.getLayerName())) {
                     doc.append("</a>");
                 }
    @@ -745,7 +752,7 @@ private void makeTaskList(StringBuilder doc, TileLayer tl, boolean listAll) {
             if (tasks) {
                 doc.append("</table>");
             }
    -        doc.append("<p><a href=\"./" + layerName + "\">Refresh list</a></p>\n");
    +        doc.append("<p><a href=\"./" + escapeHtml4(layerName) + "\">Refresh list</a></p>\n");
         }
     
         private String toTimeString(long timeSeconds, final long tilesDone, final long tilesTotal) {
    @@ -786,7 +793,7 @@ private String toTimeString(long timeSeconds, final long tilesDone, final long t
         private String makeThreadKillForm(Long key, TileLayer tl) {
             String ret =
                     "<form form id=\"kill\" action=\"./"
    -                        + tl.getName()
    +                        + escapeHtml4(tl.getName())
                             + "\" method=\"post\">"
                             + "<input type=\"hidden\" name=\"kill_thread\"  value=\"1\" />"
                             + "<input type=\"hidden\" name=\"thread_id\"  value=\""
    @@ -814,7 +821,7 @@ private String makeKillallThreadsForm(TileLayer tl, boolean listAll) {
     
             doc.append("<table><tr><td>");
             doc.append("<form form id=\"list\" action=\"./")
    -                .append(layerName)
    +                .append(escapeHtml4(layerName))
                     .append("\" method=\"post\">\n");
             doc.append("List ");
             doc.append("<select name=\"list\" onchange=\"this.form.submit();\">\n");
    @@ -839,15 +846,15 @@ private String makeKillallThreadsForm(TileLayer tl, boolean listAll) {
             doc.append("</td></tr><tr><td>");
     
             doc.append("<form form id=\"kill\" action=\"./")
    -                .append(layerName)
    +                .append(escapeHtml4(layerName))
                     .append("\" method=\"post\">\n");
             doc.append("<span>Kill \n");
             doc.append("<select name=\"kill_all\">\n");
             doc.append("<option value=\"all\">all</option>\n");
             doc.append("<option value=\"running\">running</option>\n");
             doc.append("<option value=\"pending\">pending</option>\n");
             doc.append("</select>\n");
    -        doc.append(" Tasks for Layer '").append(layerName).append("'.");
    +        doc.append(" Tasks for Layer '").append(escapeHtml4(layerName)).append("'.");
             doc.append("<input type=\"submit\" value=\" Submit\">");
             doc.append("</span>\n");
             doc.append("</form>\n");
    
  • geowebcache/rest/src/test/java/org/geowebcache/rest/service/FormServiceTest.java+42 0 modified
    @@ -18,20 +18,27 @@
     import static org.easymock.EasyMock.expect;
     import static org.easymock.EasyMock.replay;
     import static org.easymock.EasyMock.verify;
    +import static org.hamcrest.Matchers.containsString;
     import static org.hamcrest.Matchers.equalTo;
     import static org.hamcrest.Matchers.hasProperty;
    +import static org.hamcrest.Matchers.not;
    +import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertThat;
     import static org.junit.Assert.assertTrue;
     
     import java.util.Arrays;
     import java.util.Collections;
     import java.util.HashMap;
     import java.util.HashSet;
    +import java.util.List;
     import java.util.Map;
     import java.util.Set;
     import org.apache.commons.collections4.iterators.EmptyIterator;
     import org.easymock.EasyMock;
     import org.geowebcache.MockWepAppContextRule;
    +import org.geowebcache.filter.parameters.ParameterFilter;
    +import org.geowebcache.filter.parameters.RegexParameterFilter;
    +import org.geowebcache.filter.parameters.StringParameterFilter;
     import org.geowebcache.grid.BoundingBox;
     import org.geowebcache.grid.GridSubset;
     import org.geowebcache.layer.TileLayer;
    @@ -58,6 +65,41 @@ public void setUp() throws Exception {
             service.setTileBreeder(breeder);
         }
     
    +    @Test
    +    public void testEscaping() throws Exception {
    +        String unescapedLayer = "layer\"><";
    +        String escapedLayer = "layer&quot;&gt;&lt;";
    +        String unescapedString = "string\"><";
    +        String escapedString = "string&quot;&gt;&lt;";
    +        String unescapedRegex = "regex\"><";
    +        String escapedRegex = "regex&quot;&gt;&lt;";
    +        StringParameterFilter stringFilter = new StringParameterFilter();
    +        stringFilter.setKey(unescapedString);
    +        RegexParameterFilter regexFilter = new RegexParameterFilter();
    +        regexFilter.setKey(unescapedRegex);
    +        List<ParameterFilter> filters = Arrays.asList(stringFilter, regexFilter);
    +
    +        TileLayer tl = EasyMock.createMock("tl", TileLayer.class);
    +        expect(breeder.findTileLayer(unescapedLayer)).andReturn(tl);
    +        expect(tl.getName()).andStubReturn(unescapedLayer);
    +        expect(breeder.getRunningAndPendingTasks()).andReturn(Collections.emptyIterator()).times(2);
    +        expect(tl.getGridSubsets()).andReturn(Collections.emptySet()).times(4);
    +        expect(tl.getMimeTypes()).andReturn(Collections.emptyList());
    +        expect(tl.getParameterFilters()).andReturn(filters);
    +        replay(tl, breeder);
    +        ResponseEntity<?> response = service.handleGet(null, unescapedLayer);
    +        verify(tl, breeder);
    +
    +        assertEquals(HttpStatus.OK, response.getStatusCode());
    +        String body = (String) response.getBody();
    +        assertThat(body, not(containsString(unescapedLayer)));
    +        assertThat(body, containsString(escapedLayer));
    +        assertThat(body, not(containsString(unescapedString)));
    +        assertThat(body, containsString(escapedString));
    +        assertThat(body, not(containsString(unescapedRegex)));
    +        assertThat(body, containsString(escapedRegex));
    +    }
    +
         @Test
         public void testKill() {
             Map<String, String> form = new HashMap<>();
    
9d010e09c784

[GWC-1172] Improve handling special characters in the GWC Seed Form

https://github.com/GeoWebCache/geowebcacheSteve IkeokaOct 10, 2023via ghsa
2 files changed · +66 17
  • geowebcache/rest/src/main/java/org/geowebcache/rest/service/FormService.java+24 17 modified
    @@ -18,6 +18,7 @@
      */
     package org.geowebcache.rest.service;
     
    +import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;
     import static org.geowebcache.seed.TileBreeder.TILE_FAILURE_RETRY_COUNT_DEFAULT;
     import static org.geowebcache.seed.TileBreeder.TILE_FAILURE_RETRY_WAIT_TIME_DEFAULT;
     import static org.geowebcache.seed.TileBreeder.TOTAL_FAILURES_BEFORE_ABORTING_DEFAULT;
    @@ -356,7 +357,10 @@ private void makeModifiableParameters(StringBuilder doc, TileLayer tl) {
                 String key = pf.getKey();
                 String defaultValue = pf.getDefaultValue();
                 List<String> legalValues = pf.getLegalValues();
    -            doc.append("<tr><td>").append(key.toUpperCase()).append(": ").append("</td><td>");
    +            doc.append("<tr><td>")
    +                    .append(escapeHtml4(key.toUpperCase()))
    +                    .append(": ")
    +                    .append("</td><td>");
                 String parameterId = "parameter_" + key;
                 if (pf instanceof StringParameterFilter) {
                     Map<String, String> keysValues = makeParametersMap(defaultValue, legalValues);
    @@ -487,7 +491,7 @@ private void makeBboxHints(StringBuilder doc, TileLayer tl) {
                 GridSubset subset = tl.getGridSubset(gridSetId);
                 doc.append(
                         "<li>"
    -                            + gridSetId
    +                            + escapeHtml4(gridSetId)
                                 + ":   "
                                 + subset.getOriginalExtent().toString()
                                 + "</li>\n");
    @@ -501,11 +505,11 @@ private void makeTextInput(StringBuilder doc, String id, int size) {
         private void makeTextInput(StringBuilder doc, String id, int size, String defaultValue) {
             doc.append(
                     "<input name=\""
    -                        + id
    +                        + escapeHtml4(id)
                             + "\" type=\"text\" size=\""
                             + size
                             + "\" value=\""
    -                        + defaultValue
    +                        + escapeHtml4(defaultValue)
                             + "\"/>");
         }
     
    @@ -603,7 +607,7 @@ private void makeGridSetPulldown(StringBuilder doc, TileLayer tl) {
     
         private void makePullDown(
                 StringBuilder doc, String id, Map<String, String> keysValues, String defaultKey) {
    -        doc.append("<select name=\"" + id + "\">\n");
    +        doc.append("<select name=\"" + escapeHtml4(id) + "\">\n");
     
             Iterator<Map.Entry<String, String>> iter = keysValues.entrySet().iterator();
     
    @@ -612,16 +616,16 @@ private void makePullDown(
                 if (entry.getKey().equals(defaultKey)) {
                     doc.append(
                             "<option value=\""
    -                                + entry.getValue()
    +                                + escapeHtml4(entry.getValue())
                                     + "\" selected=\"selected\">"
    -                                + entry.getKey()
    +                                + escapeHtml4(entry.getKey())
                                     + "</option>\n");
                 } else {
                     doc.append(
                             "<option value=\""
    -                                + entry.getValue()
    +                                + escapeHtml4(entry.getValue())
                                     + "\">"
    -                                + entry.getKey()
    +                                + escapeHtml4(entry.getKey())
                                     + "</option>\n");
                 }
             }
    @@ -631,7 +635,10 @@ private void makePullDown(
     
         private void makeFormHeader(StringBuilder doc, TileLayer tl) {
             doc.append("<h4>Create a new task:</h4>\n");
    -        doc.append("<form id=\"seed\" action=\"./" + tl.getName() + "\" method=\"post\">\n");
    +        doc.append(
    +                "<form id=\"seed\" action=\"./"
    +                        + escapeHtml4(tl.getName())
    +                        + "\" method=\"post\">\n");
             doc.append("<table border=\"0\" cellspacing=\"10\">\n");
         }
     
    @@ -720,9 +727,9 @@ private void makeTaskList(StringBuilder doc, TileLayer tl, boolean listAll) {
                 doc.append("<td style=\"text-align:right\">").append(task.getTaskId()).append("</td>");
                 doc.append("<td>");
                 if (!layerName.equals(task.getLayerName())) {
    -                doc.append("<a href=\"./").append(task.getLayerName()).append("\">");
    +                doc.append("<a href=\"./").append(escapeHtml4(task.getLayerName())).append("\">");
                 }
    -            doc.append(task.getLayerName());
    +            doc.append(escapeHtml4(task.getLayerName()));
                 if (!layerName.equals(task.getLayerName())) {
                     doc.append("</a>");
                 }
    @@ -745,7 +752,7 @@ private void makeTaskList(StringBuilder doc, TileLayer tl, boolean listAll) {
             if (tasks) {
                 doc.append("</table>");
             }
    -        doc.append("<p><a href=\"./" + layerName + "\">Refresh list</a></p>\n");
    +        doc.append("<p><a href=\"./" + escapeHtml4(layerName) + "\">Refresh list</a></p>\n");
         }
     
         private String toTimeString(long timeSeconds, final long tilesDone, final long tilesTotal) {
    @@ -786,7 +793,7 @@ private String toTimeString(long timeSeconds, final long tilesDone, final long t
         private String makeThreadKillForm(Long key, TileLayer tl) {
             String ret =
                     "<form form id=\"kill\" action=\"./"
    -                        + tl.getName()
    +                        + escapeHtml4(tl.getName())
                             + "\" method=\"post\">"
                             + "<input type=\"hidden\" name=\"kill_thread\"  value=\"1\" />"
                             + "<input type=\"hidden\" name=\"thread_id\"  value=\""
    @@ -814,7 +821,7 @@ private String makeKillallThreadsForm(TileLayer tl, boolean listAll) {
     
             doc.append("<table><tr><td>");
             doc.append("<form form id=\"list\" action=\"./")
    -                .append(layerName)
    +                .append(escapeHtml4(layerName))
                     .append("\" method=\"post\">\n");
             doc.append("List ");
             doc.append("<select name=\"list\" onchange=\"this.form.submit();\">\n");
    @@ -839,15 +846,15 @@ private String makeKillallThreadsForm(TileLayer tl, boolean listAll) {
             doc.append("</td></tr><tr><td>");
     
             doc.append("<form form id=\"kill\" action=\"./")
    -                .append(layerName)
    +                .append(escapeHtml4(layerName))
                     .append("\" method=\"post\">\n");
             doc.append("<span>Kill \n");
             doc.append("<select name=\"kill_all\">\n");
             doc.append("<option value=\"all\">all</option>\n");
             doc.append("<option value=\"running\">running</option>\n");
             doc.append("<option value=\"pending\">pending</option>\n");
             doc.append("</select>\n");
    -        doc.append(" Tasks for Layer '").append(layerName).append("'.");
    +        doc.append(" Tasks for Layer '").append(escapeHtml4(layerName)).append("'.");
             doc.append("<input type=\"submit\" value=\" Submit\">");
             doc.append("</span>\n");
             doc.append("</form>\n");
    
  • geowebcache/rest/src/test/java/org/geowebcache/rest/service/FormServiceTest.java+42 0 modified
    @@ -18,20 +18,27 @@
     import static org.easymock.EasyMock.expect;
     import static org.easymock.EasyMock.replay;
     import static org.easymock.EasyMock.verify;
    +import static org.hamcrest.Matchers.containsString;
     import static org.hamcrest.Matchers.equalTo;
     import static org.hamcrest.Matchers.hasProperty;
    +import static org.hamcrest.Matchers.not;
    +import static org.junit.Assert.assertEquals;
     import static org.junit.Assert.assertThat;
     import static org.junit.Assert.assertTrue;
     
     import java.util.Arrays;
     import java.util.Collections;
     import java.util.HashMap;
     import java.util.HashSet;
    +import java.util.List;
     import java.util.Map;
     import java.util.Set;
     import org.apache.commons.collections4.iterators.EmptyIterator;
     import org.easymock.EasyMock;
     import org.geowebcache.MockWepAppContextRule;
    +import org.geowebcache.filter.parameters.ParameterFilter;
    +import org.geowebcache.filter.parameters.RegexParameterFilter;
    +import org.geowebcache.filter.parameters.StringParameterFilter;
     import org.geowebcache.grid.BoundingBox;
     import org.geowebcache.grid.GridSubset;
     import org.geowebcache.layer.TileLayer;
    @@ -58,6 +65,41 @@ public void setUp() throws Exception {
             service.setTileBreeder(breeder);
         }
     
    +    @Test
    +    public void testEscaping() throws Exception {
    +        String unescapedLayer = "layer\"><";
    +        String escapedLayer = "layer&quot;&gt;&lt;";
    +        String unescapedString = "string\"><";
    +        String escapedString = "string&quot;&gt;&lt;";
    +        String unescapedRegex = "regex\"><";
    +        String escapedRegex = "regex&quot;&gt;&lt;";
    +        StringParameterFilter stringFilter = new StringParameterFilter();
    +        stringFilter.setKey(unescapedString);
    +        RegexParameterFilter regexFilter = new RegexParameterFilter();
    +        regexFilter.setKey(unescapedRegex);
    +        List<ParameterFilter> filters = Arrays.asList(stringFilter, regexFilter);
    +
    +        TileLayer tl = EasyMock.createMock("tl", TileLayer.class);
    +        expect(breeder.findTileLayer(unescapedLayer)).andReturn(tl);
    +        expect(tl.getName()).andStubReturn(unescapedLayer);
    +        expect(breeder.getRunningAndPendingTasks()).andReturn(Collections.emptyIterator()).times(2);
    +        expect(tl.getGridSubsets()).andReturn(Collections.emptySet()).times(4);
    +        expect(tl.getMimeTypes()).andReturn(Collections.emptyList());
    +        expect(tl.getParameterFilters()).andReturn(filters);
    +        replay(tl, breeder);
    +        ResponseEntity<?> response = service.handleGet(null, unescapedLayer);
    +        verify(tl, breeder);
    +
    +        assertEquals(HttpStatus.OK, response.getStatusCode());
    +        String body = (String) response.getBody();
    +        assertThat(body, not(containsString(unescapedLayer)));
    +        assertThat(body, containsString(escapedLayer));
    +        assertThat(body, not(containsString(unescapedString)));
    +        assertThat(body, containsString(escapedString));
    +        assertThat(body, not(containsString(unescapedRegex)));
    +        assertThat(body, containsString(escapedRegex));
    +    }
    +
         @Test
         public void testKill() {
             Map<String, String> form = new HashMap<>();
    

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

News mentions

0

No linked articles in our index yet.