diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java b/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java index a31edf60c92..9af6e27e4d3 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/DevModeHandler.java @@ -35,7 +35,7 @@ public interface DevModeHandler extends RequestHandler { * Prepare a HTTP connection against webpack-dev-server. * * @param path - * the file to request + * the file to request, needs to be safe * @param method * the http method to use * @return the connection diff --git a/flow-server/src/main/java/com/vaadin/flow/server/HandlerHelper.java b/flow-server/src/main/java/com/vaadin/flow/server/HandlerHelper.java index b7784a2d472..5edfe83647c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/HandlerHelper.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/HandlerHelper.java @@ -15,6 +15,7 @@ */ package com.vaadin.flow.server; +import javax.servlet.http.HttpServletRequest; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -28,8 +29,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; - import com.vaadin.flow.component.UI; import com.vaadin.flow.server.communication.PwaHandler; import com.vaadin.flow.server.frontend.FrontendUtils; diff --git a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/DevModeHandlerImpl.java b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/DevModeHandlerImpl.java index 2f699647aaa..bd6dd6855b2 100644 --- a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/DevModeHandlerImpl.java +++ b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/DevModeHandlerImpl.java @@ -18,7 +18,6 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -48,8 +47,8 @@ import com.vaadin.flow.di.Lookup; import com.vaadin.flow.internal.BrowserLiveReload; import com.vaadin.flow.internal.BrowserLiveReloadAccessor; -import com.vaadin.flow.internal.Pair; import com.vaadin.flow.internal.DevModeHandler; +import com.vaadin.flow.internal.Pair; import com.vaadin.flow.server.ExecutionFailedException; import com.vaadin.flow.server.HandlerHelper; import com.vaadin.flow.server.InitParameters; @@ -99,6 +98,10 @@ public final class DevModeHandlerImpl private static final AtomicReference atomicHandler = new AtomicReference<>(); + // webpack dev-server allows " character if passed through, need to + // explicitly check requests for it + private static final Pattern WEBPACK_ILLEGAL_CHAR_PATTERN = Pattern + .compile("\"|%22"); // It's not possible to know whether webpack is ready unless reading output // messages. When webpack finishes, it writes either a `Compiled` or a // `Failed` in the last line @@ -316,7 +319,9 @@ public boolean serveDevModeRequest(HttpServletRequest request, // a valid request for webpack-dev-server should start with '/VAADIN/' String requestFilename = request.getPathInfo(); - if (HandlerHelper.isPathUnsafe(requestFilename)) { + if (HandlerHelper.isPathUnsafe(requestFilename) + || WEBPACK_ILLEGAL_CHAR_PATTERN.matcher(requestFilename) + .find()) { getLogger().info("Blocked attempt to access file: {}", requestFilename); response.setStatus(HttpServletResponse.SC_FORBIDDEN); @@ -393,6 +398,7 @@ private boolean checkWebpackConnection() { @Override public HttpURLConnection prepareConnection(String path, String method) throws IOException { + // path should have been checked at this point for any outside requests URL uri = new URL(WEBPACK_HOST + ":" + getPort() + path); HttpURLConnection connection = (HttpURLConnection) uri.openConnection(); connection.setRequestMethod(method); diff --git a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeHandlerImplTest.java b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeHandlerImplTest.java index 9b93a0a1507..5e81ea91eef 100644 --- a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeHandlerImplTest.java +++ b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeHandlerImplTest.java @@ -21,12 +21,12 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.net.ConnectException; +import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -640,6 +640,46 @@ public void devModeNotReady_handleRequest_returnsHtml() throws Exception { Mockito.verify(response).setContentType("text/html;charset=utf-8"); } + @Test + public void serveDevModeRequest_uriForDevmodeGizmo_goesToWebpack() + throws Exception { + HttpServletRequest request = prepareRequest( + "/VAADIN/build/vaadin-devmodeGizmo-f679dbf313191ec3d018.cache.js"); + HttpServletResponse response = prepareResponse(); + + final String manifestJsonResponse = "{ \"sw.js\": " + + "\"sw.js\", \"index.html\": \"index.html\" }"; + int port = prepareHttpServer(0, HTTP_OK, manifestJsonResponse); + + DevModeHandlerImpl devModeHandler = DevModeHandlerImpl.start(port, + createDevModeLookup(), npmFolder, + CompletableFuture.completedFuture(null)); + devModeHandler.join(); + + assertTrue(devModeHandler.serveDevModeRequest(request, response)); + assertEquals(HTTP_OK, responseStatus); + } + + @Test + public void serveDevModeRequest_uriWithScriptInjected_returnsImmediatelyAndSetsForbiddenStatus() + throws Exception { + HttpServletRequest request = prepareRequest( + "/VAADIN/build/vaadin-devmodeGizmo-f679dbf313191ec3d018.cache%3f%22onload=%22alert(1)"); + HttpServletResponse response = prepareResponse(); + + final String manifestJsonResponse = "{ \"sw.js\": " + + "\"sw.js\", \"index.html\": \"index.html\" }"; + int port = prepareHttpServer(0, HTTP_OK, manifestJsonResponse); + + DevModeHandlerImpl devModeHandler = DevModeHandlerImpl.start(port, + createDevModeLookup(), npmFolder, + CompletableFuture.completedFuture(null)); + devModeHandler.join(); + + assertTrue(devModeHandler.serveDevModeRequest(request, response)); + assertEquals(HTTP_FORBIDDEN, responseStatus); + } + @Test public void serveDevModeRequest_uriWithDirectoryChangeWithSlash_returnsImmediatelyAndSetsForbiddenStatus() throws IOException {