diff --git a/SOURCES/tomcat-7.0.76-CVE-2020-1938.patch b/SOURCES/tomcat-7.0.76-CVE-2020-1938.patch new file mode 100644 index 0000000..0201bb7 --- /dev/null +++ b/SOURCES/tomcat-7.0.76-CVE-2020-1938.patch @@ -0,0 +1,393 @@ +diff -up ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java.orig ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java +--- ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java.orig 2020-03-03 14:24:23.418779786 -0500 ++++ ./java/org/apache/coyote/ajp/AbstractAjpProcessor.java 2020-03-03 14:26:28.555466694 -0500 +@@ -23,7 +23,12 @@ import java.net.InetAddress; + import java.security.NoSuchProviderException; + import java.security.cert.CertificateFactory; + import java.security.cert.X509Certificate; ++import java.util.Collections; ++import java.util.HashSet; ++import java.util.Set; + import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.regex.Matcher; ++import java.util.regex.Pattern; + + import javax.servlet.http.HttpServletResponse; + +@@ -80,6 +85,9 @@ public abstract class AbstractAjpProcess + protected static final byte[] pongMessageArray; + + ++ private static final Set javaxAttributes; ++ ++ + static { + // Allocate the end message array + AjpMessage endMessage = new AjpMessage(16); +@@ -120,6 +128,14 @@ public abstract class AbstractAjpProcess + pongMessageArray = new byte[pongMessage.getLen()]; + System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray, + 0, pongMessage.getLen()); ++ ++ // Build the Set of javax attributes ++ Set s = new HashSet(); ++ s.add("javax.servlet.request.cipher_suite"); ++ s.add("javax.servlet.request.key_size"); ++ s.add("javax.servlet.request.ssl_session"); ++ s.add("javax.servlet.request.X509Certificate"); ++ javaxAttributes= Collections.unmodifiableSet(s); + } + + +@@ -307,9 +323,16 @@ public abstract class AbstractAjpProcess + /** + * Required secret. + */ ++ @Deprecated + protected String requiredSecret = null; ++ protected String secret = null; ++ public void setSecret(String secret) { ++ this.secret = secret; ++ this.requiredSecret = secret; ++ } ++ @Deprecated + public void setRequiredSecret(String requiredSecret) { +- this.requiredSecret = requiredSecret; ++ setSecret(requiredSecret); + } + + +@@ -326,9 +349,15 @@ public abstract class AbstractAjpProcess + public String getClientCertProvider() { return clientCertProvider; } + public void setClientCertProvider(String s) { this.clientCertProvider = s; } + +- // --------------------------------------------------------- Public Methods ++ ++ private Pattern allowedRequestAttributesPatternPattern; ++ public void setAllowedRequestAttributesPatternPattern(Pattern allowedRequestAttributesPatternPattern) { ++ this.allowedRequestAttributesPatternPattern = allowedRequestAttributesPatternPattern; ++ } + + ++ // --------------------------------------------------------- Public Methods ++ + /** + * Send an action to the connector. + * +@@ -827,7 +856,7 @@ public abstract class AbstractAjpProcess + } + + // Decode extra attributes +- boolean secret = false; ++ boolean secretPresentInRequest = false; + byte attributeCode; + while ((attributeCode = requestHeaderMessage.getByte()) + != Constants.SC_A_ARE_DONE) { +@@ -856,8 +885,25 @@ public abstract class AbstractAjpProcess + } + } else if(n.equals(Constants.SC_A_SSL_PROTOCOL)) { + request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v); ++ } else if (n.equals("JK_LB_ACTIVATION")) { ++ request.setAttribute(n, v); ++ } else if (javaxAttributes.contains(n)) { ++ request.setAttribute(n, v); + } else { +- request.setAttribute(n, v ); ++ // All 'known' attributes will be processed by the previous ++ // blocks. Any remaining attribute is an 'arbitrary' one. ++ if (allowedRequestAttributesPatternPattern == null) { ++ response.setStatus(403); ++ setErrorState(ErrorState.CLOSE_CLEAN, null); ++ } else { ++ Matcher m = allowedRequestAttributesPatternPattern.matcher(n); ++ if (m.matches()) { ++ request.setAttribute(n, v); ++ } else { ++ response.setStatus(403); ++ setErrorState(ErrorState.CLOSE_CLEAN, null); ++ } ++ } + } + break; + +@@ -930,9 +976,9 @@ public abstract class AbstractAjpProcess + + case Constants.SC_A_SECRET: + requestHeaderMessage.getBytes(tmpMB); +- if (requiredSecret != null) { +- secret = true; +- if (!tmpMB.equals(requiredSecret)) { ++ if (secret != null) { ++ secretPresentInRequest = true; ++ if (!tmpMB.equals(secret)) { + response.setStatus(403); + setErrorState(ErrorState.CLOSE_CLEAN, null); + } +@@ -948,7 +994,7 @@ public abstract class AbstractAjpProcess + } + + // Check if secret was submitted if required +- if ((requiredSecret != null) && !secret) { ++ if ((secret != null) && !secretPresentInRequest) { + response.setStatus(403); + setErrorState(ErrorState.CLOSE_CLEAN, null); + } +diff -up ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java.orig ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java +--- ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java.orig 2020-03-03 14:24:23.422779776 -0500 ++++ ./java/org/apache/coyote/ajp/AbstractAjpProtocol.java 2020-03-03 14:39:25.219521977 -0500 +@@ -16,6 +16,8 @@ + */ + package org.apache.coyote.ajp; + ++import java.util.regex.Pattern; ++ + import org.apache.coyote.AbstractProtocol; + import org.apache.coyote.Processor; + import org.apache.coyote.http11.upgrade.servlet31.HttpUpgradeHandler; +@@ -85,8 +87,61 @@ public abstract class AbstractAjpProtoco + * Required secret. + */ + protected String requiredSecret = null; ++ private String secret = null; ++ /** ++ * Set the secret that must be included with every request. ++ * ++ * @param secret The required secret ++ */ ++ public void setSecret(String secret) { ++ this.secret = secret; ++ this.requiredSecret = secret; ++ } ++ protected String getSecret() { ++ return secret; ++ } ++ /** ++ * Set the required secret that must be included with every request. ++ * ++ * @param requiredSecret The required secret ++ * ++ * @deprecated Replaced by {@link #setSecret(String)}. ++ * Will be removed in Tomcat 11 onwards ++ */ ++ @Deprecated + public void setRequiredSecret(String requiredSecret) { +- this.requiredSecret = requiredSecret; ++ setSecret(requiredSecret); ++ } ++ /** ++ * @return The current secret ++ * ++ * @deprecated Replaced by {@link #getSecret()}. ++ * Will be removed in Tomcat 11 onwards ++ */ ++ @Deprecated ++ protected String getRequiredSecret() { ++ return getSecret(); ++ } ++ ++ ++ private boolean secretRequired = false; ++ public void setSecretRequired(boolean secretRequired) { ++ this.secretRequired = secretRequired; ++ } ++ public boolean getSecretRequired() { ++ return secretRequired; ++ } ++ ++ ++ private Pattern allowedRequestAttributesPatternPattern; ++ public void setAllowedRequestAttributesPattern(String allowedRequestAttributesPattern) { ++ this.allowedRequestAttributesPatternPattern = Pattern.compile(allowedRequestAttributesPattern); ++ } ++ public String getAllowedRequestAttributesPattern() { ++ return allowedRequestAttributesPatternPattern.pattern(); ++ } ++ protected Pattern getAllowedRequestAttributesPatternPattern() { ++ return allowedRequestAttributesPatternPattern; + } + + +@@ -136,4 +191,16 @@ public abstract class AbstractAjpProtoco + return null; + } + } ++ ++ ++ @Override ++ public void start() throws Exception { ++ if (getSecretRequired()) { ++ String secret = getSecret(); ++ if (secret == null || secret.length() == 0) { ++ throw new IllegalArgumentException(sm.getString("ajpprotocol.nosecret")); ++ } ++ } ++ super.start(); ++ } + } +diff -up ./java/org/apache/coyote/ajp/AjpAprProtocol.java.orig ./java/org/apache/coyote/ajp/AjpAprProtocol.java +--- ./java/org/apache/coyote/ajp/AjpAprProtocol.java.orig 2020-03-03 14:24:23.426779766 -0500 ++++ ./java/org/apache/coyote/ajp/AjpAprProtocol.java 2020-03-03 14:26:28.556466692 -0500 +@@ -148,10 +148,11 @@ public class AjpAprProtocol extends Abst + processor.setAjpFlush(proto.getAjpFlush()); + processor.setTomcatAuthentication(proto.tomcatAuthentication); + processor.setTomcatAuthorization(proto.getTomcatAuthorization()); +- processor.setRequiredSecret(proto.requiredSecret); ++ processor.setSecret(proto.getSecret()); + processor.setKeepAliveTimeout(proto.getKeepAliveTimeout()); + processor.setClientCertProvider(proto.getClientCertProvider()); + processor.setMaxCookieCount(proto.getMaxCookieCount()); ++ processor.setAllowedRequestAttributesPatternPattern(proto.getAllowedRequestAttributesPatternPattern()); + register(processor); + return processor; + } +diff -up ./java/org/apache/coyote/ajp/AjpNioProtocol.java.orig ./java/org/apache/coyote/ajp/AjpNioProtocol.java +--- ./java/org/apache/coyote/ajp/AjpNioProtocol.java.orig 2020-03-03 14:24:23.430779756 -0500 ++++ ./java/org/apache/coyote/ajp/AjpNioProtocol.java 2020-03-03 14:26:28.556466692 -0500 +@@ -178,10 +178,11 @@ public class AjpNioProtocol extends Abst + processor.setAjpFlush(proto.getAjpFlush()); + processor.setTomcatAuthentication(proto.tomcatAuthentication); + processor.setTomcatAuthorization(proto.getTomcatAuthorization()); +- processor.setRequiredSecret(proto.requiredSecret); ++ processor.setSecret(proto.getSecret()); + processor.setKeepAliveTimeout(proto.getKeepAliveTimeout()); + processor.setClientCertProvider(proto.getClientCertProvider()); + processor.setMaxCookieCount(proto.getMaxCookieCount()); ++ processor.setAllowedRequestAttributesPatternPattern(proto.getAllowedRequestAttributesPatternPattern()); + register(processor); + return processor; + } +diff -up ./java/org/apache/coyote/ajp/AjpProtocol.java.orig ./java/org/apache/coyote/ajp/AjpProtocol.java +--- ./java/org/apache/coyote/ajp/AjpProtocol.java.orig 2020-03-03 14:24:23.435779743 -0500 ++++ ./java/org/apache/coyote/ajp/AjpProtocol.java 2020-03-03 14:26:28.556466692 -0500 +@@ -136,10 +136,11 @@ public class AjpProtocol extends Abstrac + processor.setAjpFlush(proto.getAjpFlush()); + processor.setTomcatAuthentication(proto.tomcatAuthentication); + processor.setTomcatAuthorization(proto.getTomcatAuthorization()); +- processor.setRequiredSecret(proto.requiredSecret); ++ processor.setSecret(proto.getSecret()); + processor.setKeepAliveTimeout(proto.getKeepAliveTimeout()); + processor.setClientCertProvider(proto.getClientCertProvider()); + processor.setMaxCookieCount(proto.getMaxCookieCount()); ++ processor.setAllowedRequestAttributesPatternPattern(proto.getAllowedRequestAttributesPatternPattern()); + register(processor); + return processor; + } +diff -up ./java/org/apache/coyote/ajp/LocalStrings.properties.orig ./java/org/apache/coyote/ajp/LocalStrings.properties +--- ./java/org/apache/coyote/ajp/LocalStrings.properties.orig 2020-03-03 14:24:23.439779733 -0500 ++++ ./java/org/apache/coyote/ajp/LocalStrings.properties 2020-03-03 14:26:13.057505470 -0500 +@@ -14,6 +14,7 @@ + # limitations under the License. + ajpprotocol.endpoint.starterror=Error starting endpoint + ajpprotocol.init=Initializing Coyote AJP/1.3 on {0} ++ajpprotocol.nosecret=The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid. + ajpprotocol.start=Starting Coyote AJP/1.3 on {0} + ajpprotocol.failedwrite=Socket write failed + ajpprotocol.request.register=Error registering request processor in JMX +diff -up ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java.orig ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java +--- ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java.orig 2020-03-03 14:24:23.442779726 -0500 ++++ ./test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java 2020-03-03 14:26:28.556466692 -0500 +@@ -30,14 +30,26 @@ import javax.servlet.http.HttpServletReq + import javax.servlet.http.HttpServletResponse; + + import org.junit.Assert; ++import org.junit.Before; + import org.junit.Test; + + import org.apache.catalina.Context; ++import org.apache.catalina.connector.Connector; + import org.apache.catalina.startup.Tomcat; + import org.apache.catalina.startup.TomcatBaseTest; + + public class TestAbstractAjpProcessor extends TomcatBaseTest { + ++ @Before ++ @Override ++ public void setUp() throws Exception { ++ super.setUp(); ++ ++ Connector c = getTomcatInstance().getConnector(); ++ c.setProperty("secretRequired", "false"); ++ c.setProperty("allowedRequestAttributesPattern", "MYATTRIBUTE.*"); ++ } ++ + @Override + protected String getProtocol() { + /* +diff -up ./webapps/docs/changelog.xml.orig ./webapps/docs/changelog.xml +--- ./webapps/docs/changelog.xml.orig 2020-03-03 14:24:23.446779716 -0500 ++++ ./webapps/docs/changelog.xml 2020-03-03 14:41:14.526247697 -0500 +@@ -57,6 +57,25 @@ + They eventually become mixed with the numbered issues. (I.e., numbered + issues do not "pop up" wrt. others). + --> ++
++ ++ ++ ++ Rename the requiredSecret attribute of the AJP/1.3 ++ Connector to secret and add a new attribute ++ secretRequired that defaults to true. When ++ secretRequired is true the AJP/1.3 Connector ++ will not start unless the secret attribute is configured to ++ a non-null, non-zero length String. (markt) ++ ++ ++ Add a new attribute, allowedRequestAttributesPattern to ++ the AJP/1.3 Connector. Requests with unreconised attributes will be ++ blocked with a 403. (markt) ++ ++ ++ ++
+
+ + +diff -up ./webapps/docs/config/ajp.xml.orig ./webapps/docs/config/ajp.xml +--- ./webapps/docs/config/ajp.xml.orig 2020-03-03 14:24:23.448779711 -0500 ++++ ./webapps/docs/config/ajp.xml 2020-03-03 14:39:44.506473588 -0500 +@@ -312,6 +312,25 @@ + interface.

+ + ++ ++

The AJP protocol passes some information from the reverse proxy to the ++ AJP connector using request attributes. These attributes are:

++
    ++
  • javax.servlet.request.cipher_suite
  • ++
  • javax.servlet.request.key_size
  • ++
  • javax.servlet.request.ssl_session
  • ++
  • javax.servlet.request.X509Certificate
  • ++
  • AJP_LOCAL_ADDR
  • ++
  • AJP_REMOTE_PORT
  • ++
  • AJP_SSL_PROTOCOL
  • ++
  • JK_LB_ACTIVATION
  • ++
++

The AJP protocol supports the passing of arbitrary request attributes. ++ Requests containing arbitrary request attributes will be rejected with a ++ 403 response unless the entire attribute name matches this regular ++ expression. If not specified, the default value is null.

++
++ + +

Controls when the socket used by the connector is bound. By default it + is bound when the connector is initiated and unbound when the connector is +@@ -436,8 +455,18 @@ + expected concurrent requests (synchronous and asynchronous).

+
+ +- ++ +

Only requests from workers with this secret keyword will be accepted. ++ The default value is null. This attrbute must be specified ++ with a non-null, non-zero length value unless ++ secretRequired is explicitly configured to be ++ false.

++
++ ++ ++

If this attribute is true, the AJP Connector will only ++ start if the secret attribute is configured with a ++ non-null, non-zero length value. The default value is false. +

+
+ diff --git a/SPECS/tomcat.spec b/SPECS/tomcat.spec index a4e5ad6..0581db7 100644 --- a/SPECS/tomcat.spec +++ b/SPECS/tomcat.spec @@ -54,7 +54,7 @@ Name: tomcat Epoch: 0 Version: %{major_version}.%{minor_version}.%{micro_version} -Release: 10%{?dist} +Release: 11%{?dist} Summary: Apache Servlet/JSP Engine, RI for Servlet %{servletspec}/JSP %{jspspec} API Group: System Environment/Daemons @@ -101,6 +101,7 @@ Patch13: %{name}-7.0.76-CVE-2018-8014.patch Patch14: %{name}-7.0.76-CVE-2018-8034.patch Patch15: %{name}-7.0.76-rhbz-1588703.patch Patch16: %{name}-7.0.76-rhbz-1455483.patch +Patch17: %{name}-7.0.76-CVE-2020-1938.patch BuildArch: noarch @@ -262,6 +263,7 @@ find . -type f \( -name "*.bat" -o -name "*.class" -o -name Thumbs.db -o -name " %patch14 -p0 %patch15 -p0 %patch16 -p0 +%patch17 -p0 %{__ln_s} $(build-classpath jakarta-taglibs-core) webapps/examples/WEB-INF/lib/jstl.jar %{__ln_s} $(build-classpath jakarta-taglibs-standard) webapps/examples/WEB-INF/lib/standard.jar @@ -706,6 +708,9 @@ fi %attr(0644,root,root) %{_unitdir}/%{name}-jsvc.service %changelog +* Tue Mar 03 2020 Coty Sutherland 0:7.0.76-11 +- Resolves: rhbz#1806801 CVE-2020-1938 tomcat: Apache Tomcat AJP File Read/Inclusion Vulnerability + * Tue Sep 03 2019 Coty Sutherland 0:7.0.76-10 - Resolves: rhbz#1748541 Bump tomcat release number