From 640e596ef979649dfc84ab471e439a8e758c6f70 Mon Sep 17 00:00:00 2001 From: bibi samina Date: Thu, 2 Jul 2026 09:36:15 +0530 Subject: [PATCH 1/2] normalize module base path before resolving rpc policy resource --- .../user/server/rpc/RemoteServiceServlet.java | 28 +++++++++++++-- .../server/rpc/RemoteServiceServletTest.java | 35 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java index 1bd73e142ef..a9bb7a24407 100644 --- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java +++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.text.ParseException; import java.util.HashMap; @@ -59,7 +60,7 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, String modulePath = null; if (moduleBaseURL != null) { try { - modulePath = new URL(moduleBaseURL).getPath(); + modulePath = normalizeModulePath(new URL(moduleBaseURL).getPath()); } catch (MalformedURLException ex) { // log the information, we will default servlet.log("Malformed moduleBaseURL: " + moduleBaseURL, ex); @@ -137,6 +138,26 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, return serializationPolicy; } + /** + * Collapses "." and ".." segments in a module base path. The path is derived + * from the client-supplied module base URL, which is later concatenated with + * the strong name and file suffix to locate a resource; normalizing here + * ensures a crafted URL such as {@code http://host/ctx/../../WEB-INF/foo} + * cannot walk outside the module directory before the "same web application" + * containment check is applied. Returns {@code null} if the path cannot be + * parsed. + */ + private static String normalizeModulePath(String path) { + if (path == null) { + return null; + } + try { + return URI.create(path).normalize().getRawPath(); + } catch (IllegalArgumentException ex) { + return null; + } + } + private static final SerializationPolicyClient CODE_SERVER_CLIENT = new SerializationPolicyClient(5000, 5000); @@ -226,7 +247,10 @@ protected String getRequestModuleBasePath() { if (header == null) { return null; } - String path = new URL(header).getPath(); + String path = normalizeModulePath(new URL(header).getPath()); + if (path == null) { + return null; + } String contextPath = getThreadLocalRequest().getContextPath(); if (!path.startsWith(contextPath)) { return null; diff --git a/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java index c76f252a2ea..9d89e609cdd 100644 --- a/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java +++ b/user/test/com/google/gwt/user/server/rpc/RemoteServiceServletTest.java @@ -366,6 +366,41 @@ public String getVirtualServerName() { } } + /** + * A crafted moduleBaseURL containing ".." segments must not be able to walk + * out of the module directory when the strong name and file suffix are + * appended to build the serialization policy resource path. + */ + public void testDoGetSerializationPolicy_ModuleBaseUrlTraversal() + throws ServletException { + final StringBuilder requestedResource = new StringBuilder(); + MockServletContext mockContext = new MockServletContext() { + @Override + public InputStream getResourceAsStream(String resource) { + requestedResource.append(resource); + return null; + } + }; + MockServletConfig mockConfig = new MockServletConfig(mockContext); + + RemoteServiceServlet rss = new RemoteServiceServlet(); + + MockHttpServletRequestContextPath mockRequest = new MockHttpServletRequestContextPath(); + rss.init(mockConfig); + + mockRequest.contextPath = "/MyModule"; + + SerializationPolicy serializationPolicy = rss.doGetSerializationPolicy( + mockRequest, "http://www.google.com/MyModule/../../../secret", "12345"); + + // The traversal walks above the context path, so no policy is loaded and + // the resource path handed to the container never escapes the module dir. + assertNull(serializationPolicy); + assertNotNull(mockContext.messageLogged); + assertFalse("resource path must not contain a traversal segment: " + + requestedResource, requestedResource.toString().contains("..")); + } + public void testDoGetSerializationPolicy_FailToOpenMD5Resource() throws ServletException { MockServletContext mockContext = new MockServletContext() { From 48a2d0fb83cdd20cb18995ae661481a47317f2fe Mon Sep 17 00:00:00 2001 From: bibi samina Date: Fri, 3 Jul 2026 09:49:34 +0530 Subject: [PATCH 2/2] parse module base URL directly with URI constructor --- .../user/server/rpc/RemoteServiceServlet.java | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java index a9bb7a24407..5f92ac7b20b 100644 --- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java +++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java @@ -25,9 +25,8 @@ import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; import java.net.URI; -import java.net.URL; +import java.net.URISyntaxException; import java.text.ParseException; import java.util.HashMap; import java.util.Map; @@ -60,8 +59,8 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, String modulePath = null; if (moduleBaseURL != null) { try { - modulePath = normalizeModulePath(new URL(moduleBaseURL).getPath()); - } catch (MalformedURLException ex) { + modulePath = normalizeModulePath(moduleBaseURL); + } catch (URISyntaxException ex) { // log the information, we will default servlet.log("Malformed moduleBaseURL: " + moduleBaseURL, ex); } @@ -139,23 +138,17 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, } /** - * Collapses "." and ".." segments in a module base path. The path is derived - * from the client-supplied module base URL, which is later concatenated with - * the strong name and file suffix to locate a resource; normalizing here - * ensures a crafted URL such as {@code http://host/ctx/../../WEB-INF/foo} - * cannot walk outside the module directory before the "same web application" - * containment check is applied. Returns {@code null} if the path cannot be - * parsed. + * Returns the path of a client-supplied module base URL with "." and ".." + * segments collapsed. The path is later concatenated with the strong name and + * file suffix to locate a resource; normalizing here ensures a crafted URL + * such as {@code http://host/ctx/../../WEB-INF/foo} cannot walk outside the + * module directory before the "same web application" containment check is + * applied. The returned path may be {@code null} for a URL without a path + * component. */ - private static String normalizeModulePath(String path) { - if (path == null) { - return null; - } - try { - return URI.create(path).normalize().getRawPath(); - } catch (IllegalArgumentException ex) { - return null; - } + private static String normalizeModulePath(String moduleBaseURL) + throws URISyntaxException { + return new URI(moduleBaseURL).normalize().getRawPath(); } private static final SerializationPolicyClient CODE_SERVER_CLIENT = @@ -247,7 +240,7 @@ protected String getRequestModuleBasePath() { if (header == null) { return null; } - String path = normalizeModulePath(new URL(header).getPath()); + String path = normalizeModulePath(header); if (path == null) { return null; } @@ -256,7 +249,7 @@ protected String getRequestModuleBasePath() { return null; } return path.substring(contextPath.length()); - } catch (MalformedURLException e) { + } catch (URISyntaxException e) { return null; } }