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.