From 379f7d877c16a4370d9197d89420c344d8b4bb3e Mon Sep 17 00:00:00 2001 From: starlet-dx <15929766099@163.com> Date: Mon, 3 Mar 2025 17:59:33 +0800 Subject: [PATCH] Fix CVE-2017-12196,CVE-2019-10184 and CVE-2019-10212 (cherry picked from commit 329cb3a1e2aa3f6f9845ab228d2034edbb7a53f4) --- CVE-2017-12196-1.patch | 151 +++++++++++++++++ CVE-2017-12196-2.patch | 38 +++++ CVE-2019-10184-pre.patch | 23 +++ CVE-2019-10184.patch | 345 +++++++++++++++++++++++++++++++++++++++ CVE-2019-10212.patch | 26 +++ undertow.spec | 10 +- 6 files changed, 592 insertions(+), 1 deletion(-) create mode 100644 CVE-2017-12196-1.patch create mode 100644 CVE-2017-12196-2.patch create mode 100644 CVE-2019-10184-pre.patch create mode 100644 CVE-2019-10184.patch create mode 100644 CVE-2019-10212.patch diff --git a/CVE-2017-12196-1.patch b/CVE-2017-12196-1.patch new file mode 100644 index 0000000..9edc15d --- /dev/null +++ b/CVE-2017-12196-1.patch @@ -0,0 +1,151 @@ +From facb33a5cedaf4b7b96d3840a08210370a806870 Mon Sep 17 00:00:00 2001 +From: Stuart Douglas +Date: Tue, 3 Oct 2017 13:29:48 +0200 +Subject: [PATCH] UNDERTOW-1190 client can use bogus uri in digest + authentication + +--- + .../impl/DigestAuthenticationMechanism.java | 19 +++++++- + .../DigestAuthenticationAuthTestCase.java | 45 ++++++++++++++++++- + .../security/digest/DigestAuthTestCase.java | 7 +-- + 3 files changed, 65 insertions(+), 6 deletions(-) + +diff --git a/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java b/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java +index e5a75bd834..e01724b44b 100644 +--- a/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java ++++ b/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java +@@ -42,6 +42,7 @@ + import io.undertow.util.HeaderMap; + import io.undertow.util.Headers; + import io.undertow.util.HexConverter; ++import io.undertow.util.StatusCodes; + + import java.nio.charset.StandardCharsets; + import java.security.MessageDigest; +@@ -181,7 +182,7 @@ public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exch + return AuthenticationMechanismOutcome.NOT_ATTEMPTED; + } + +- public AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exchange, final SecurityContext securityContext) { ++ private AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exchange, final SecurityContext securityContext) { + DigestContext context = exchange.getAttachment(DigestContext.ATTACHMENT_KEY); + Map parsedHeader = context.getParsedHeader(); + // Step 1 - Verify the set of tokens received to ensure valid values. +@@ -231,7 +232,21 @@ public AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exch + return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; + } + +- // TODO - Validate the URI ++ if(parsedHeader.containsKey(DigestAuthorizationToken.DIGEST_URI)) { ++ String uri = parsedHeader.get(DigestAuthorizationToken.DIGEST_URI); ++ String requestURI = exchange.getRequestURI(); ++ if(!exchange.getQueryString().isEmpty()) { ++ requestURI = requestURI + "?" + exchange.getQueryString(); ++ } ++ if(!uri.equals(requestURI)) { ++ //just end the auth process ++ exchange.setStatusCode(StatusCodes.BAD_REQUEST); ++ exchange.endExchange(); ++ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; ++ } ++ } else { ++ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; ++ } + + if (parsedHeader.containsKey(DigestAuthorizationToken.OPAQUE)) { + if (!OPAQUE_VALUE.equals(parsedHeader.get(DigestAuthorizationToken.OPAQUE))) { +diff --git a/core/src/test/java/io/undertow/server/security/DigestAuthenticationAuthTestCase.java b/core/src/test/java/io/undertow/server/security/DigestAuthenticationAuthTestCase.java +index f9fa2b6aa4..d1aff564e1 100644 +--- a/core/src/test/java/io/undertow/server/security/DigestAuthenticationAuthTestCase.java ++++ b/core/src/test/java/io/undertow/server/security/DigestAuthenticationAuthTestCase.java +@@ -163,7 +163,7 @@ private static String createAuthorizationLine(final String userName, final Strin + sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"userOne\"").append(","); + sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); + sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); +- sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); ++ sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"" + uri + "\","); + String nonceCountHex = toHex(nonceCount); + String response = createResponse(userName, REALM_NAME, password, method, uri, nonce, nonceCountHex, cnonce); + sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(response).append("\","); +@@ -243,6 +243,49 @@ static void _testDigestSuccess() throws Exception { + } + } + ++ /** ++ * Test for a successful authentication. ++ * ++ * Also makes two additional calls to demonstrate nonce re-use with an incrementing nonce count. ++ */ ++ @Test ++ public void testDigestBadUri() throws Exception { ++ _testDigestBadUri(); ++ } ++ ++ static void _testDigestBadUri() throws Exception { ++ TestHttpClient client = new TestHttpClient(); ++ HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL()); ++ HttpResponse result = client.execute(get); ++ assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); ++ Header[] values = result.getHeaders(WWW_AUTHENTICATE.toString()); ++ String value = getAuthHeader(DIGEST, values); ++ ++ Map parsedHeader = DigestWWWAuthenticateToken.parseHeader(value.substring(7)); ++ assertEquals(REALM_NAME, parsedHeader.get(DigestWWWAuthenticateToken.REALM)); ++ assertEquals(DigestAlgorithm.MD5.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.ALGORITHM)); ++ assertEquals(DigestQop.AUTH.getToken(), parsedHeader.get(DigestWWWAuthenticateToken.MESSAGE_QOP)); ++ ++ String clientNonce = createNonce(); ++ int nonceCount = 1; ++ String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); ++ String opaque = parsedHeader.get(DigestWWWAuthenticateToken.OPAQUE); ++ assertNotNull(opaque); ++ // Send 5 requests with an incrementing nonce count on each call. ++ for (int i = 0; i < 5; i++) { ++ client = new TestHttpClient(); ++ get = new HttpGet(DefaultServer.getDefaultServerURL()); ++ ++ int thisNonceCount = nonceCount++; ++ String authorization = createAuthorizationLine("userOne", "passwordOne", "GET", "/badUri", nonce, thisNonceCount, ++ clientNonce, opaque); ++ ++ get.addHeader(AUTHORIZATION.toString(), authorization); ++ result = client.execute(get); ++ assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); ++ ++ } ++ } + /** + * Test for a failed authentication where a bad username is provided. + */ +diff --git a/servlet/src/test/java/io/undertow/servlet/test/security/digest/DigestAuthTestCase.java b/servlet/src/test/java/io/undertow/servlet/test/security/digest/DigestAuthTestCase.java +index d2ac85e0c6..7164634c47 100644 +--- a/servlet/src/test/java/io/undertow/servlet/test/security/digest/DigestAuthTestCase.java ++++ b/servlet/src/test/java/io/undertow/servlet/test/security/digest/DigestAuthTestCase.java +@@ -119,7 +119,8 @@ public void testAuthType() throws Exception { + + public void testCall(final String path, final String expectedResponse) throws Exception { + TestHttpClient client = new TestHttpClient(); +- String url = DefaultServer.getDefaultServerURL() + "/servletContext/secured/" + path; ++ String servletPath = "/servletContext/secured/" + path; ++ String url = DefaultServer.getDefaultServerURL() + servletPath; + HttpGet get = new HttpGet(url); + HttpResponse result = client.execute(get); + assertEquals(StatusCodes.UNAUTHORIZED, result.getStatusLine().getStatusCode()); +@@ -134,7 +135,7 @@ public void testCall(final String path, final String expectedResponse) throws Ex + + String nonce = parsedHeader.get(DigestWWWAuthenticateToken.NONCE); + +- String clientResponse = createResponse("user1", REALM_NAME, "password1", "GET", "/", nonce); ++ String clientResponse = createResponse("user1", REALM_NAME, "password1", "GET", servletPath, nonce); + + client = new TestHttpClient(); + get = new HttpGet(url); +@@ -143,7 +144,7 @@ public void testCall(final String path, final String expectedResponse) throws Ex + sb.append(DigestAuthorizationToken.USERNAME.getName()).append("=").append("\"user1\"").append(","); + sb.append(DigestAuthorizationToken.REALM.getName()).append("=\"").append(REALM_NAME).append("\","); + sb.append(DigestAuthorizationToken.NONCE.getName()).append("=\"").append(nonce).append("\","); +- sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"/\","); ++ sb.append(DigestAuthorizationToken.DIGEST_URI.getName()).append("=\"" + servletPath + "\","); + sb.append(DigestAuthorizationToken.RESPONSE.getName()).append("=\"").append(clientResponse).append("\""); + + get.addHeader(AUTHORIZATION.toString(), sb.toString()); diff --git a/CVE-2017-12196-2.patch b/CVE-2017-12196-2.patch new file mode 100644 index 0000000..19a3e52 --- /dev/null +++ b/CVE-2017-12196-2.patch @@ -0,0 +1,38 @@ +From 8804170ce3186bdd83b486959399ec7ac0f59d0f Mon Sep 17 00:00:00 2001 +From: Stuart Douglas +Date: Mon, 11 Dec 2017 10:51:51 +1100 +Subject: [PATCH] UNDERTOW-1190 handle absolute URI in the digest mechanism + +--- + .../impl/DigestAuthenticationMechanism.java | 18 ++++++++++++++---- + 1 file changed, 14 insertions(+), 4 deletions(-) + +diff --git a/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java b/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java +index e01724b44b..972a0cb0e4 100644 +--- a/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java ++++ b/core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java +@@ -239,10 +239,20 @@ private AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exc + requestURI = requestURI + "?" + exchange.getQueryString(); + } + if(!uri.equals(requestURI)) { +- //just end the auth process +- exchange.setStatusCode(StatusCodes.BAD_REQUEST); +- exchange.endExchange(); +- return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; ++ //it is possible we were given an absolute URI ++ //we reconstruct the URI from the host header to make sure they match up ++ //I am not sure if this is overly strict, however I think it is better ++ //to be safe than sorry ++ requestURI = exchange.getRequestURL(); ++ if(!exchange.getQueryString().isEmpty()) { ++ requestURI = requestURI + "?" + exchange.getQueryString(); ++ } ++ if(!uri.equals(requestURI)) { ++ //just end the auth process ++ exchange.setStatusCode(StatusCodes.BAD_REQUEST); ++ exchange.endExchange(); ++ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; ++ } + } + } else { + return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; diff --git a/CVE-2019-10184-pre.patch b/CVE-2019-10184-pre.patch new file mode 100644 index 0000000..41afec4 --- /dev/null +++ b/CVE-2019-10184-pre.patch @@ -0,0 +1,23 @@ +From 16833ca1ea7e2235e40129078f94f935b3f7e446 Mon Sep 17 00:00:00 2001 +From: Stuart Douglas +Date: Thu, 15 Sep 2016 09:50:02 +1000 +Subject: [PATCH] UNDERTOW-837 Incorrect welcome file rewrite outside of the + root directory + +--- + .../io/undertow/servlet/handlers/ServletInitialHandler.java | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java b/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java +index 3873ea6c32..0e8e3ecb15 100644 +--- a/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java ++++ b/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java +@@ -170,7 +170,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception { + //this can only happen if the path ends with a / + //otherwise there would be a redirect instead + exchange.setRelativePath(info.getRewriteLocation()); +- exchange.setRequestPath(exchange.getRequestPath() + info.getRewriteLocation()); ++ exchange.setRequestPath(exchange.getResolvedPath() + info.getRewriteLocation()); + } + + final HttpServletResponseImpl response = new HttpServletResponseImpl(exchange, servletContext); diff --git a/CVE-2019-10184.patch b/CVE-2019-10184.patch new file mode 100644 index 0000000..4834ace --- /dev/null +++ b/CVE-2019-10184.patch @@ -0,0 +1,345 @@ +From d2715e3afa13f50deaa19643676816ce391551e9 Mon Sep 17 00:00:00 2001 +From: Lin Gao +Date: Wed, 24 Jul 2019 22:03:01 +0800 +Subject: [PATCH] [UNDERTOW-1578] 401 Unauthorized should be returned when + requesting a protected directory without trailing slash + +--- + .../servlet/core/DeploymentManagerImpl.java | 2 + + .../servlet/handlers/RedirectDirHandler.java | 71 ++++++++ + .../handlers/ServletInitialHandler.java | 29 +-- + .../SecurityRedirectTestCase.java | 168 ++++++++++++++++++ + 4 files changed, 244 insertions(+), 26 deletions(-) + create mode 100644 servlet/src/main/java/io/undertow/servlet/handlers/RedirectDirHandler.java + create mode 100644 servlet/src/test/java/io/undertow/servlet/test/defaultservlet/SecurityRedirectTestCase.java + +diff --git a/servlet/src/main/java/io/undertow/servlet/core/DeploymentManagerImpl.java b/servlet/src/main/java/io/undertow/servlet/core/DeploymentManagerImpl.java +index 1906c4eaa0..9c49c4ad53 100644 +--- a/servlet/src/main/java/io/undertow/servlet/core/DeploymentManagerImpl.java ++++ b/servlet/src/main/java/io/undertow/servlet/core/DeploymentManagerImpl.java +@@ -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); +diff --git a/servlet/src/main/java/io/undertow/servlet/handlers/RedirectDirHandler.java b/servlet/src/main/java/io/undertow/servlet/handlers/RedirectDirHandler.java +new file mode 100644 +index 0000000000..576eb11a87 +--- /dev/null ++++ b/servlet/src/main/java/io/undertow/servlet/handlers/RedirectDirHandler.java +@@ -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); ++ } ++ ++} +diff --git a/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java b/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java +index 82a3ad7360..7edc7e493c 100644 +--- a/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java ++++ b/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java +@@ -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); +diff --git a/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/SecurityRedirectTestCase.java b/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/SecurityRedirectTestCase.java +new file mode 100644 +index 0000000000..8fa9323177 +--- /dev/null ++++ b/servlet/src/test/java/io/undertow/servlet/test/defaultservlet/SecurityRedirectTestCase.java +@@ -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(); ++ } ++ } ++ ++} diff --git a/CVE-2019-10212.patch b/CVE-2019-10212.patch new file mode 100644 index 0000000..1cb24a8 --- /dev/null +++ b/CVE-2019-10212.patch @@ -0,0 +1,26 @@ +From 8b63e258502f9f55b33b2e0b02a2e24cf5d2f1c1 Mon Sep 17 00:00:00 2001 +From: Paramvir Jindal +Date: Fri, 11 Oct 2019 11:51:22 +0530 +Subject: [PATCH] UNDERTOW-1576: BASIC auth password is output as plain text at + DEBUG level logging in BasicAuthenticationMechanism + +--- + .../undertow/security/impl/BasicAuthenticationMechanism.java | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/core/src/main/java/io/undertow/security/impl/BasicAuthenticationMechanism.java b/core/src/main/java/io/undertow/security/impl/BasicAuthenticationMechanism.java +index 7042e8ff66..94e786427e 100644 +--- a/core/src/main/java/io/undertow/security/impl/BasicAuthenticationMechanism.java ++++ b/core/src/main/java/io/undertow/security/impl/BasicAuthenticationMechanism.java +@@ -151,9 +151,9 @@ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, + } + + plainChallenge = new String(decode.array(), decode.arrayOffset(), decode.limit(), charset); +- UndertowLogger.SECURITY_LOGGER.debugf("Found basic auth header %s (decoded using charset %s) in %s", plainChallenge, charset, exchange); ++ UndertowLogger.SECURITY_LOGGER.debugf("Found basic auth header (decoded using charset %s) in %s", charset, exchange); + } catch (IOException e) { +- UndertowLogger.SECURITY_LOGGER.debugf(e, "Failed to decode basic auth header %s in %s", base64Challenge, exchange); ++ UndertowLogger.SECURITY_LOGGER.debugf(e, "Failed to decode basic auth header in %s", exchange); + } + int colonPos; + if (plainChallenge != null && (colonPos = plainChallenge.indexOf(COLON)) > -1) { diff --git a/undertow.spec b/undertow.spec index 11dc02d..c55f6cb 100644 --- a/undertow.spec +++ b/undertow.spec @@ -2,7 +2,7 @@ %global namedversion %{version}%{?namedreltag} Name: undertow Version: 1.4.0 -Release: 9 +Release: 10 Summary: Java web server using non-blocking IO License: ASL 2.0 URL: http://undertow.io/ @@ -17,6 +17,11 @@ Patch5: CVE-2021-3690.patch Patch6: CVE-2023-1973.patch Patch7: CVE-2023-5379.patch Patch8: CVE-2024-4109.patch +Patch9: CVE-2017-12196-1.patch +Patch10: CVE-2017-12196-2.patch +Patch11: CVE-2019-10184-pre.patch +Patch12: CVE-2019-10184.patch +Patch13: CVE-2019-10212.patch BuildArch: noarch Epoch: 1 BuildRequires: maven-local mvn(junit:junit) mvn(org.eclipse.jetty.alpn:alpn-api) @@ -78,6 +83,9 @@ export CXXFLAGS="${RPM_OPT_FLAGS}" %license LICENSE.txt %changelog +* Tue Mar 04 2025 yaoxin <1024769339@qq.com> - 1:1.4.0-10 +- Fix CVE-2017-12196,CVE-2019-10184 and CVE-2019-10212 + * Tue Dec 17 2024 liyajie - 1:1.4.0-9 - Fix CVE-2024-4109 -- Gitee