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 1bd73e142e..5f92ac7b20 100644 --- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java +++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java @@ -25,8 +25,8 @@ import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.text.ParseException; import java.util.HashMap; import java.util.Map; @@ -59,8 +59,8 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, String modulePath = null; if (moduleBaseURL != null) { try { - modulePath = 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); } @@ -137,6 +137,20 @@ static SerializationPolicy loadSerializationPolicy(HttpServlet servlet, return serializationPolicy; } + /** + * 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 moduleBaseURL) + throws URISyntaxException { + return new URI(moduleBaseURL).normalize().getRawPath(); + } + private static final SerializationPolicyClient CODE_SERVER_CLIENT = new SerializationPolicyClient(5000, 5000); @@ -226,13 +240,16 @@ protected String getRequestModuleBasePath() { if (header == null) { return null; } - String path = new URL(header).getPath(); + String path = normalizeModulePath(header); + if (path == null) { + return null; + } String contextPath = getThreadLocalRequest().getContextPath(); if (!path.startsWith(contextPath)) { return null; } return path.substring(contextPath.length()); - } catch (MalformedURLException e) { + } catch (URISyntaxException e) { 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 c76f252a2e..9d89e609cd 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() {