High severityNVD Advisory· Published Jul 25, 2019· Updated Aug 4, 2024
CVE-2019-10184
CVE-2019-10184
Description
undertow before version 2.0.23.Final is vulnerable to an information leak issue. Web apps may have their directory structures predicted through requests without trailing slashes via the api.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.undertow:undertow-servletMaven | < 2.0.23 | 2.0.23 |
Affected products
1- Range: fixed in 2.0.23.Final
Patches
15fa7ac68c0e4Merge pull request #794 from gaol/Trailing_Slash_Fix
4 files changed · +244 −26
servlet/src/main/java/io/undertow/servlet/core/DeploymentManagerImpl.java+2 −0 modified@@ -71,6 +71,7 @@ import io.undertow.servlet.api.ThreadSetupHandler; import io.undertow.servlet.api.WebResourceCollection; import io.undertow.servlet.handlers.CrawlerSessionManagerHandler; +import io.undertow.servlet.handlers.RedirectDirHandler; import io.undertow.servlet.handlers.ServletDispatchingHandler; import io.undertow.servlet.handlers.ServletHandler; import io.undertow.servlet.handlers.ServletInitialHandler; @@ -218,6 +219,7 @@ public Void call(HttpServerExchange exchange, Object ignore) throws Exception { HttpHandler wrappedHandlers = ServletDispatchingHandler.INSTANCE; wrappedHandlers = wrapHandlers(wrappedHandlers, deploymentInfo.getInnerHandlerChainWrappers()); + wrappedHandlers = new RedirectDirHandler(wrappedHandlers, deployment.getServletPaths()); if(!deploymentInfo.isSecurityDisabled()) { HttpHandler securityHandler = setupSecurityHandlers(wrappedHandlers); wrappedHandlers = new PredicateHandler(DispatcherTypePredicate.REQUEST, securityHandler, wrappedHandlers);
servlet/src/main/java/io/undertow/servlet/handlers/RedirectDirHandler.java+71 −0 added@@ -0,0 +1,71 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2019 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.undertow.servlet.handlers; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; +import io.undertow.util.Methods; +import io.undertow.util.RedirectBuilder; +import io.undertow.util.StatusCodes; + +/** + * Handler that redirects the directory requests without trailing slash to the one append trailing slash. + * + * @author Lin Gao + */ +public class RedirectDirHandler implements HttpHandler { + + private static final String HTTP2_UPGRADE_PREFIX = "h2"; + + private final HttpHandler next; + private final ServletPathMatches paths; + + public RedirectDirHandler(HttpHandler next, ServletPathMatches paths) { + this.next = next; + this.paths = paths; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + final String path = exchange.getRelativePath(); + final ServletPathMatch info = paths.getServletHandlerByPath(path); + // https://issues.jboss.org/browse/WFLY-3439 + // if the request is an upgrade request then we don't want to redirect + // as there is a good chance the web socket client won't understand the redirect + // we make an exception for HTTP2 upgrade requests, as this would have already be handled at + // the connector level if it was going to be handled. + String upgradeString = exchange.getRequestHeaders().getFirst(Headers.UPGRADE); + boolean isUpgradeRequest = upgradeString != null && !upgradeString.startsWith(HTTP2_UPGRADE_PREFIX); + if (info.getType() == ServletPathMatch.Type.REDIRECT && !isUpgradeRequest) { + // UNDERTOW-89 + // we redirect on GET requests to the root context to add an / to the end + if (exchange.getRequestMethod().equals(Methods.GET) || exchange.getRequestMethod().equals(Methods.HEAD)) { + exchange.setStatusCode(StatusCodes.FOUND); + } else { + exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); + } + exchange.getResponseHeaders().put(Headers.LOCATION, + RedirectBuilder.redirect(exchange, exchange.getRelativePath() + "/", true)); + return; + } + next.handleRequest(exchange); + } + +}
servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java+3 −26 modified@@ -39,11 +39,8 @@ import io.undertow.servlet.spec.HttpServletResponseImpl; import io.undertow.servlet.spec.RequestDispatcherImpl; import io.undertow.servlet.spec.ServletContextImpl; -import io.undertow.util.Headers; import io.undertow.util.HttpString; -import io.undertow.util.Methods; import io.undertow.util.Protocols; -import io.undertow.util.RedirectBuilder; import io.undertow.util.StatusCodes; import org.xnio.ChannelListener; import org.xnio.Option; @@ -80,8 +77,6 @@ */ public class ServletInitialHandler implements HttpHandler, ServletDispatcher { - private static final String HTTP2_UPGRADE_PREFIX = "h2"; - private static final RuntimePermission PERMISSION = new RuntimePermission("io.undertow.servlet.CREATE_INITIAL_HANDLER"); private final HttpHandler next; @@ -149,30 +144,12 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception { return; } final ServletPathMatch info = paths.getServletHandlerByPath(path); - //https://issues.jboss.org/browse/WFLY-3439 - //if the request is an upgrade request then we don't want to redirect - //as there is a good chance the web socket client won't understand the redirect - //we make an exception for HTTP2 upgrade requests, as this would have already be handled at - //the connector level if it was going to be handled. - String upgradeString = exchange.getRequestHeaders().getFirst(Headers.UPGRADE); - boolean isUpgradeRequest = upgradeString != null && !upgradeString.startsWith(HTTP2_UPGRADE_PREFIX); - if (info.getType() == ServletPathMatch.Type.REDIRECT && !isUpgradeRequest) { - //UNDERTOW-89 - //we redirect on GET requests to the root context to add an / to the end - if(exchange.getRequestMethod().equals(Methods.GET) || exchange.getRequestMethod().equals(Methods.HEAD)) { - exchange.setStatusCode(StatusCodes.FOUND); - } else { - exchange.setStatusCode(StatusCodes.TEMPORARY_REDIRECT); - } - exchange.getResponseHeaders().put(Headers.LOCATION, RedirectBuilder.redirect(exchange, exchange.getRelativePath() + "/", true)); - return; - } else if (info.getType() == ServletPathMatch.Type.REWRITE) { - //this can only happen if the path ends with a / - //otherwise there would be a redirect instead + if (info.getType() == ServletPathMatch.Type.REWRITE) { + // this can only happen if the path ends with a / + // otherwise there would be a redirect instead exchange.setRelativePath(info.getRewriteLocation()); exchange.setRequestPath(exchange.getResolvedPath() + info.getRewriteLocation()); } - final HttpServletResponseImpl response = new HttpServletResponseImpl(exchange, servletContext); final HttpServletRequestImpl request = new HttpServletRequestImpl(exchange, servletContext); final ServletRequestContext servletRequestContext = new ServletRequestContext(servletContext.getDeployment(), request, response, info);
servlet/src/test/java/io/undertow/servlet/test/defaultservlet/SecurityRedirectTestCase.java+168 −0 added@@ -0,0 +1,168 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2019 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.undertow.servlet.test.defaultservlet; + +import static io.undertow.util.Headers.AUTHORIZATION; +import static io.undertow.util.Headers.BASIC; +import static io.undertow.util.Headers.LOCATION; +import static io.undertow.util.Headers.WWW_AUTHENTICATE; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.LoginConfig; +import io.undertow.servlet.api.SecurityConstraint; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.WebResourceCollection; +import io.undertow.servlet.test.path.ServletPathMappingTestCase; +import io.undertow.servlet.test.security.constraint.ServletIdentityManager; +import io.undertow.servlet.test.util.TestClassIntrospector; +import io.undertow.servlet.test.util.TestResourceLoader; +import io.undertow.testutils.DefaultServer; +import io.undertow.testutils.HttpClientUtils; +import io.undertow.util.FlexBase64; +import io.undertow.util.StatusCodes; + +/** + * TestCase on redirect with or without trailing slash when requesting protected path. + * + * @author Lin Gao + */ +@RunWith(DefaultServer.class) +public class SecurityRedirectTestCase { + + @BeforeClass + public static void setup() throws ServletException { + + final PathHandler root = new PathHandler(); + final ServletContainer container = ServletContainer.Factory.newInstance(); + + ServletIdentityManager identityManager = new ServletIdentityManager(); + identityManager.addUser("user1", "password1", "role1"); + + DeploymentInfo builder = new DeploymentInfo() + .setClassIntrospecter(TestClassIntrospector.INSTANCE) + .setClassLoader(ServletPathMappingTestCase.class.getClassLoader()) + .setContextPath("/servletContext") + .setDeploymentName("servletContext.war") + .setResourceManager(new TestResourceLoader(SecurityRedirectTestCase.class)) + .addWelcomePages("index.html") + .setIdentityManager(identityManager) + .setLoginConfig(new LoginConfig("BASIC", "Test Realm")) + .addSecurityConstraint(new SecurityConstraint() + .addRoleAllowed("role1") + .addWebResourceCollection(new WebResourceCollection() + .addUrlPatterns("/index.html", "/filterpath/*"))); + + DeploymentManager manager = container.addDeployment(builder); + manager.deploy(); + root.addPrefixPath(builder.getContextPath(), manager.start()); + DefaultServer.setRootHandler(root); + } + + @SuppressWarnings("deprecation") + @Test + public void testSecurityWithWelcomeFileRedirect() throws IOException { + // disable following redirect + HttpClient client = HttpClientBuilder.create().disableRedirectHandling().build(); + try { + HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext"); + HttpResponse result = client.execute(get); + Assert.assertEquals(StatusCodes.FOUND, result.getStatusLine().getStatusCode()); + Header[] values = result.getHeaders(LOCATION.toString()); + assertEquals(1, values.length); + assertEquals(DefaultServer.getDefaultServerURL() + "/servletContext/", values[0].getValue()); + HttpClientUtils.readResponse(result); + + get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); + result = client.execute(get); + Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); + + values = result.getHeaders(WWW_AUTHENTICATE.toString()); + assertEquals(1, values.length); + assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); + HttpClientUtils.readResponse(result); + + get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/"); + get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); + result = client.execute(get); + String response = HttpClientUtils.readResponse(result); + Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); + Assert.assertTrue(response.contains("Redirected home page")); + } finally { + client.getConnectionManager().shutdown(); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testSecurityWithoutWelcomeFileRedirect() throws IOException { + // disable following redirect + HttpClient client = HttpClientBuilder.create().disableRedirectHandling().build(); + try { + HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath"); + HttpResponse result = client.execute(get); + Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); + HttpClientUtils.readResponse(result); + + get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath/"); + result = client.execute(get); + Assert.assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); + + Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); + assertEquals(1, values.length); + assertEquals(BASIC + " realm=\"Test Realm\"", values[0].getValue()); + HttpClientUtils.readResponse(result); + + get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath"); + get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); + result = client.execute(get); + Assert.assertEquals(StatusCodes.FOUND, result.getStatusLine().getStatusCode()); + values = result.getHeaders(LOCATION.toString()); + assertEquals(1, values.length); + assertEquals(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath/", values[0].getValue()); + HttpClientUtils.readResponse(result); + + get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/filterpath/filtered.txt"); + get.addHeader(AUTHORIZATION.toString(), BASIC + " " + FlexBase64.encodeString("user1:password1".getBytes(), false)); + result = client.execute(get); + String response = HttpClientUtils.readResponse(result); + Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); + Assert.assertTrue(response.equals("Stuart")); + } finally { + client.getConnectionManager().shutdown(); + } + } + +}
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
18- access.redhat.com/errata/RHSA-2019:2935ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:2936ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:2937ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:2938ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:2998ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:3044ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:3045ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:3046ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:3050ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2020:0727ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-w69w-jvc7-wjgvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-10184ghsaADVISORY
- bugzilla.redhat.com/show_bug.cgighsax_refsource_CONFIRMWEB
- github.com/undertow-io/undertow/commit/5fa7ac68c0e4251c93056d9982db5e794e04ebfaghsaWEB
- github.com/undertow-io/undertow/pull/794ghsax_refsource_CONFIRMWEB
- issues.redhat.com/browse/UNDERTOW-1578ghsaWEB
- security.netapp.com/advisory/ntap-20220210-0016ghsaWEB
- security.netapp.com/advisory/ntap-20220210-0016/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.