CVE-2016-6816
Description
The code in Apache Tomcat 9.0.0.M1 to 9.0.0.M11, 8.5.0 to 8.5.6, 8.0.0.RC1 to 8.0.38, 7.0.0 to 7.0.72, and 6.0.0 to 6.0.47 that parsed the HTTP request line permitted invalid characters. This could be exploited, in conjunction with a proxy that also permitted the invalid characters but with a different interpretation, to inject data into the HTTP response. By manipulating the HTTP response the attacker could poison a web-cache, perform an XSS attack and/or obtain sensitive information from requests other then their own.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.tomcat:tomcat-coyoteMaven | >= 9.0.0.M1, < 9.0.0.M12 | 9.0.0.M12 |
org.apache.tomcat:tomcat-coyoteMaven | >= 8.5.0, < 8.5.8 | 8.5.8 |
org.apache.tomcat:tomcat-coyoteMaven | >= 8.0.0RC1, < 8.0.39 | 8.0.39 |
org.apache.tomcat:tomcat-coyoteMaven | >= 7.0.0, < 7.0.73 | 7.0.73 |
org.apache.tomcat:tomcat-coyoteMaven | >= 6.0.0, < 6.0.48 | 6.0.48 |
Affected products
179cpe:2.3:a:apache:tomcat:6.0.8:*:*:*:*:*:*:*+ 177 more
- cpe:2.3:a:apache:tomcat:6.0.8:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.0:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.1:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.2:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.3:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.4:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.5:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.6:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.7:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.40:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.15:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.16:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.17:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.18:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.19:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.20:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.21:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.22:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.23:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.24:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.25:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.26:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.27:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.57:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.58:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.59:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.60:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.61:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.64:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.65:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.66:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.67:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.17:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.18:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.19:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.20:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.21:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.22:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.23:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.24:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.25:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.26:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.27:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.28:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.29:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.30:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.31:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.32:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.33:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.34:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.35:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.36:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.37:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.38:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.5.0:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.9:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.10:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.11:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.12:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.13:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.14:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.15:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.16:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.17:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.18:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.19:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.20:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.21:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.22:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.23:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.24:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.25:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.26:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.27:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.28:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.29:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.30:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.31:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.32:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.33:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.34:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.35:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.36:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.37:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.38:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.39:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.41:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.42:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.43:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.44:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.45:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.46:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:6.0.47:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.0:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.1:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.2:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.3:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.4:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.5:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.6:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.7:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.8:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.9:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.10:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.11:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.12:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.13:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.14:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.28:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.29:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.30:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.31:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.32:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.33:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.34:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.35:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.36:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.37:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.38:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.39:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.40:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.41:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.42:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.43:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.44:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.45:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.46:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.47:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.48:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.49:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.50:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.51:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.52:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.53:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.54:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.55:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.56:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.62:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.63:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.68:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.69:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.70:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.71:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:7.0.72:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.0:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.1:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.2:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.3:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.4:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.5:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.6:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.7:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.8:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.9:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.10:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.11:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.12:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.13:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.14:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.15:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.0.16:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone1:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone10:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone11:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone2:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.5.1:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.5.2:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.5.3:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.5.4:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.5.5:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:8.5.6:*:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone3:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone4:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone5:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone6:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone7:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone8:*:*:*:*:*:*
- cpe:2.3:a:apache:tomcat:9.0.0:milestone9:*:*:*:*:*:*
- Apache Software Foundation/Apache Tomcatv5Range: 9.0.0.M1 to 9.0.0.M11
Patches
4cdc0a935c217Add additional checks for valid characters to the HTTP request line
7 files changed · +141 −134
java/org/apache/coyote/http11/AbstractInputBuffer.java+1 −53 modified@@ -28,62 +28,10 @@ public abstract class AbstractInputBuffer<S> implements InputBuffer{ - protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128]; - /** * The string manager for this package. */ - protected static final StringManager sm = - StringManager.getManager(Constants.Package); - - - static { - for (int i = 0; i < 128; i++) { - if (i < 32) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == 127) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '(') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ')') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '<') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '>') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '@') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ',') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ';') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ':') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\\') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\"') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '/') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '[') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ']') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '?') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '=') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '{') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '}') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ' ') { - HTTP_TOKEN_CHAR[i] = false; - } else { - HTTP_TOKEN_CHAR[i] = true; - } - } - } + protected static final StringManager sm = StringManager.getManager(Constants.Package); /**
java/org/apache/coyote/http11/InternalAprInputBuffer.java+29 −25 modified@@ -30,6 +30,7 @@ import org.apache.tomcat.jni.Status; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.SocketWrapper; @@ -70,7 +71,7 @@ public InternalAprInputBuffer(Request request, int headerBufferSize) { parsingHeader = true; swallowInput = true; - + } @@ -93,7 +94,7 @@ public InternalAprInputBuffer(Request request, int headerBufferSize) { // --------------------------------------------------------- Public Methods /** - * Recycle the input buffer. This should be called when closing the + * Recycle the input buffer. This should be called when closing the * connection. */ @Override @@ -105,14 +106,14 @@ public void recycle() { /** - * Read the request line. This function is meant to be used during the - * HTTP request header parsing. Do NOT attempt to read the request body + * Read the request line. This function is meant to be used during the + * HTTP request header parsing. Do NOT attempt to read the request body * using it. * * @throws IOException If an exception occurs during the underlying socket * read operations, or if the given buffer is not big enough to accommodate * the whole line. - * @return true if data is properly fed; false if no data is available + * @return true if data is properly fed; false if no data is available * immediately and thread should be freed */ @Override @@ -177,7 +178,7 @@ public boolean parseRequestLine(boolean useAvailableData) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, start, pos - start); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } @@ -222,15 +223,16 @@ public boolean parseRequestLine(boolean useAvailableData) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; end = pos; - } else if ((buf[pos] == Constants.CR) + } else if ((buf[pos] == Constants.CR) || (buf[pos] == Constants.LF)) { // HTTP/0.9 style request eol = true; space = true; end = pos; - } else if ((buf[pos] == Constants.QUESTION) - && (questionPos == -1)) { + } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { questionPos = pos; + } else if (HttpParser.isNotRequestTarget(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } pos++; @@ -239,7 +241,7 @@ public boolean parseRequestLine(boolean useAvailableData) request.unparsedURI().setBytes(buf, start, end - start); if (questionPos >= 0) { - request.queryString().setBytes(buf, questionPos + 1, + request.queryString().setBytes(buf, questionPos + 1, end - questionPos - 1); request.requestURI().setBytes(buf, start, questionPos - start); } else { @@ -267,7 +269,7 @@ public boolean parseRequestLine(boolean useAvailableData) // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // while (!eol) { @@ -284,6 +286,8 @@ public boolean parseRequestLine(boolean useAvailableData) if (end == 0) end = pos; eol = true; + } else if (!HttpParser.isHttpProtocol(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } pos++; @@ -295,7 +299,7 @@ public boolean parseRequestLine(boolean useAvailableData) } else { request.protocol().setString(""); } - + return true; } @@ -324,7 +328,7 @@ public boolean parseHeaders() /** * Parse an HTTP header. - * + * * @return false after reading a blank line (which indicates that the * HTTP header parsing is done */ @@ -382,7 +386,7 @@ private boolean parseHeader() if (buf[pos] == Constants.COLON) { colon = true; headerValue = headers.addValue(buf, start, pos - start); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { // If a non-token header is detected, skip the line and // ignore the header skipLine(start); @@ -488,14 +492,14 @@ private boolean parseHeader() } - + private void skipLine(int start) throws IOException { boolean eol = false; int lastRealByte = start; if (pos - 1 > start) { lastRealByte = pos - 1; } - + while (!eol) { // Read new bytes if needed @@ -519,16 +523,16 @@ private void skipLine(int start) throws IOException { lastRealByte - start + 1, Charset.forName("ISO-8859-1")))); } } - - + + // ---------------------------------------------------- InputBuffer Methods /** * Read some bytes. */ @Override - public int doRead(ByteChunk chunk, Request req) + public int doRead(ByteChunk chunk, Request req) throws IOException { if (lastActiveFilter == -1) @@ -556,11 +560,11 @@ protected boolean fill(boolean block) throws IOException { // Ignore the block parameter and just call fill return fill(); } - - + + /** * Fill the internal buffer using data from the underlying input stream. - * + * * @return false if at end of stream */ protected boolean fill() @@ -592,7 +596,7 @@ protected boolean fill() } else { if (buf.length - end < 4500) { - // In this case, the request header was really large, so we allocate a + // In this case, the request header was really large, so we allocate a // brand new one; the old one will get GCed when subsequent requests // clear all references buf = new byte[buf.length]; @@ -638,15 +642,15 @@ protected boolean fill() * This class is an input buffer which will read its data from an input * stream. */ - protected class SocketInputBuffer + protected class SocketInputBuffer implements InputBuffer { /** * Read bytes into the specified chunk. */ @Override - public int doRead(ByteChunk chunk, Request req ) + public int doRead(ByteChunk chunk, Request req ) throws IOException { if (pos >= lastValid) {
java/org/apache/coyote/http11/InternalInputBuffer.java+22 −19 modified@@ -28,6 +28,7 @@ import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.SocketWrapper; @@ -69,10 +70,10 @@ public InternalInputBuffer(Request request, int headerBufferSize) { } - + /** - * Read the request line. This function is meant to be used during the - * HTTP request header parsing. Do NOT attempt to read the request body + * Read the request line. This function is meant to be used during the + * HTTP request header parsing. Do NOT attempt to read the request body * using it. * * @throws IOException If an exception occurs during the underlying socket @@ -81,7 +82,7 @@ public InternalInputBuffer(Request request, int headerBufferSize) { */ @Override public boolean parseRequestLine(boolean useAvailableDataOnly) - + throws IOException { int start = 0; @@ -131,7 +132,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, start, pos - start); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } @@ -176,15 +177,16 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; end = pos; - } else if ((buf[pos] == Constants.CR) + } else if ((buf[pos] == Constants.CR) || (buf[pos] == Constants.LF)) { // HTTP/0.9 style request eol = true; space = true; end = pos; - } else if ((buf[pos] == Constants.QUESTION) - && (questionPos == -1)) { + } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { questionPos = pos; + } else if (HttpParser.isNotRequestTarget(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } pos++; @@ -193,7 +195,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) request.unparsedURI().setBytes(buf, start, end - start); if (questionPos >= 0) { - request.queryString().setBytes(buf, questionPos + 1, + request.queryString().setBytes(buf, questionPos + 1, end - questionPos - 1); request.requestURI().setBytes(buf, start, questionPos - start); } else { @@ -220,9 +222,8 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // - while (!eol) { // Read new bytes if needed @@ -237,6 +238,8 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (end == 0) end = pos; eol = true; + } else if (!HttpParser.isHttpProtocol(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } pos++; @@ -248,7 +251,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) } else { request.protocol().setString(""); } - + return true; } @@ -277,7 +280,7 @@ public boolean parseHeaders() /** * Parse an HTTP header. - * + * * @return false after reading a blank line (which indicates that the * HTTP header parsing is done */ @@ -335,7 +338,7 @@ private boolean parseHeader() if (buf[pos] == Constants.COLON) { colon = true; headerValue = headers.addValue(buf, start, pos - start); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { // If a non-token header is detected, skip the line and // ignore the header skipLine(start); @@ -467,7 +470,7 @@ private void skipLine(int start) throws IOException { if (pos - 1 > start) { lastRealByte = pos - 1; } - + while (!eol) { // Read new bytes if needed @@ -494,7 +497,7 @@ private void skipLine(int start) throws IOException { /** * Fill the internal buffer using data from the underlying input stream. - * + * * @return false if at end of stream */ protected boolean fill() throws IOException { @@ -521,7 +524,7 @@ protected boolean fill(boolean block) throws IOException { } else { if (buf.length - end < 4500) { - // In this case, the request header was really large, so we allocate a + // In this case, the request header was really large, so we allocate a // brand new one; the old one will get GCed when subsequent requests // clear all references buf = new byte[buf.length]; @@ -548,15 +551,15 @@ protected boolean fill(boolean block) throws IOException { * This class is an input buffer which will read its data from an input * stream. */ - protected class InputStreamInputBuffer + protected class InputStreamInputBuffer implements InputBuffer { /** * Read bytes into the specified chunk. */ @Override - public int doRead(ByteChunk chunk, Request req ) + public int doRead(ByteChunk chunk, Request req ) throws IOException { if (pos >= lastValid) {
java/org/apache/coyote/http11/InternalNioInputBuffer.java+39 −35 modified@@ -25,6 +25,7 @@ import org.apache.coyote.Request; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.NioChannel; import org.apache.tomcat.util.net.NioEndpoint; @@ -92,7 +93,7 @@ enum HeaderParsePosition { } // ----------------------------------------------------------- Constructors - + /** * Alternate constructor. @@ -137,7 +138,7 @@ public InternalNioInputBuffer(Request request, int headerBufferSize) { * Underlying socket. */ private NioChannel socket; - + /** * Selector pool, for blocking reads and blocking writes */ @@ -159,7 +160,7 @@ public InternalNioInputBuffer(Request request, int headerBufferSize) { // --------------------------------------------------------- Public Methods /** - * Recycle the input buffer. This should be called when closing the + * Recycle the input buffer. This should be called when closing the * connection. */ @Override @@ -178,7 +179,7 @@ public void recycle() { /** * End processing of current HTTP request. - * Note: All bytes of the current request should have been already + * Note: All bytes of the current request should have been already * consumed. This method only resets all the pointers so that we are ready * to parse the next HTTP request. */ @@ -195,14 +196,14 @@ public void nextRequest() { } /** - * Read the request line. This function is meant to be used during the - * HTTP request header parsing. Do NOT attempt to read the request body + * Read the request line. This function is meant to be used during the + * HTTP request header parsing. Do NOT attempt to read the request body * using it. * * @throws IOException If an exception occurs during the underlying socket * read operations, or if the given buffer is not big enough to accommodate * the whole line. - * @return true if data is properly fed; false if no data is available + * @return true if data is properly fed; false if no data is available * immediately and thread should be freed */ @Override @@ -217,7 +218,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if ( parsingRequestLinePhase == 0 ) { byte chr = 0; do { - + // Read new bytes if needed if (pos >= lastValid) { if (useAvailableDataOnly) { @@ -262,7 +263,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } pos++; @@ -289,7 +290,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) } if (parsingRequestLinePhase == 4) { // Mark the current buffer position - + int end = 0; // // Reading the URI @@ -304,21 +305,22 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; end = pos; - } else if ((buf[pos] == Constants.CR) + } else if ((buf[pos] == Constants.CR) || (buf[pos] == Constants.LF)) { // HTTP/0.9 style request parsingRequestLineEol = true; space = true; end = pos; - } else if ((buf[pos] == Constants.QUESTION) - && (parsingRequestLineQPos == -1)) { + } else if ((buf[pos] == Constants.QUESTION) && (parsingRequestLineQPos == -1)) { parsingRequestLineQPos = pos; + } else if (HttpParser.isNotRequestTarget(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } pos++; } request.unparsedURI().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); if (parsingRequestLineQPos >= 0) { - request.queryString().setBytes(buf, parsingRequestLineQPos + 1, + request.queryString().setBytes(buf, parsingRequestLineQPos + 1, end - parsingRequestLineQPos - 1); request.requestURI().setBytes(buf, parsingRequestLineStart, parsingRequestLineQPos - parsingRequestLineStart); } else { @@ -347,28 +349,30 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) // Mark the current buffer position end = 0; } - if (parsingRequestLinePhase == 6) { + if (parsingRequestLinePhase == 6) { // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // while (!parsingRequestLineEol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true, false)) //request line parsing return false; } - + if (buf[pos] == Constants.CR) { end = pos; } else if (buf[pos] == Constants.LF) { if (end == 0) end = pos; parsingRequestLineEol = true; + } else if (!HttpParser.isHttpProtocol(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } pos++; } - + if ( (end - parsingRequestLineStart) > 0) { request.protocol().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); } else { @@ -382,7 +386,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) } throw new IllegalStateException("Invalid request line parse phase:"+parsingRequestLinePhase); } - + private void expand(int newsize) { if ( newsize > buf.length ) { if (parsingHeader) { @@ -397,7 +401,7 @@ private void expand(int newsize) { buf = tmp; } } - + /** * Perform blocking read with a timeout if desired * @param timeout boolean - if we want to use the timeout data @@ -406,7 +410,7 @@ private void expand(int newsize) { * @throws IOException if a socket exception occurs * @throws EOFException if end of stream is reached */ - + private int readSocket(boolean timeout, boolean block) throws IOException { int nRead = 0; socket.getBufHandler().getReadBuffer().clear(); @@ -428,7 +432,7 @@ private int readSocket(boolean timeout, boolean block) throws IOException { socket.getIOChannel().socket().getSoTimeout()); } catch ( EOFException eof ) { nRead = -1; - } finally { + } finally { if ( selector != null ) pool.put(selector); } } else { @@ -461,7 +465,7 @@ public boolean parseHeaders() } HeaderParseStatus status = HeaderParseStatus.HAVE_MORE_HEADERS; - + do { status = parseHeader(); // Checking that @@ -490,7 +494,7 @@ public boolean parseHeaders() /** * Parse an HTTP header. - * + * * @return false after reading a blank line (which indicates that the * HTTP header parsing is done */ @@ -506,7 +510,7 @@ private HeaderParseStatus parseHeader() // Read new bytes if needed if (pos >= lastValid) { - if (!fill(true,false)) {//parse header + if (!fill(true,false)) {//parse header headerParsePos = HeaderParsePosition.HEADER_START; return HeaderParseStatus.NEED_MORE_DATA; } @@ -542,7 +546,7 @@ private HeaderParseStatus parseHeader() // Read new bytes if needed if (pos >= lastValid) { - if (!fill(true,false)) { //parse header + if (!fill(true,false)) { //parse header return HeaderParseStatus.NEED_MORE_DATA; } } @@ -557,7 +561,7 @@ private HeaderParseStatus parseHeader() headerData.realPos = pos; headerData.lastSignificantChar = pos; break; - } else if (!HTTP_TOKEN_CHAR[chr]) { + } else if (!HttpParser.isToken(chr)) { // If a non-token header is detected, skip the line and // ignore the header headerData.lastSignificantChar = pos; @@ -589,7 +593,7 @@ private HeaderParseStatus parseHeader() while (true) { // Read new bytes if needed if (pos >= lastValid) { - if (!fill(true,false)) {//parse header + if (!fill(true,false)) {//parse header //HEADER_VALUE_START return HeaderParseStatus.NEED_MORE_DATA; } @@ -612,7 +616,7 @@ private HeaderParseStatus parseHeader() // Read new bytes if needed if (pos >= lastValid) { - if (!fill(true,false)) {//parse header + if (!fill(true,false)) {//parse header //HEADER_VALUE return HeaderParseStatus.NEED_MORE_DATA; } @@ -645,7 +649,7 @@ private HeaderParseStatus parseHeader() // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) {//parse header - + //HEADER_MULTI_LINE return HeaderParseStatus.NEED_MORE_DATA; } @@ -671,7 +675,7 @@ private HeaderParseStatus parseHeader() headerData.recycle(); return HeaderParseStatus.HAVE_MORE_HEADERS; } - + public int getParsingRequestLinePhase() { return parsingRequestLinePhase; } @@ -770,7 +774,7 @@ protected void init(SocketWrapper<NioChannel> socketWrapper, /** * Fill the internal buffer using data from the underlying input stream. - * + * * @return false if at end of stream */ @Override @@ -779,7 +783,7 @@ protected boolean fill(boolean block) throws IOException, EOFException { } protected boolean fill(boolean timeout, boolean block) throws IOException, EOFException { - + boolean read = false; @@ -808,15 +812,15 @@ protected boolean fill(boolean timeout, boolean block) throws IOException, EOFEx * This class is an input buffer which will read its data from an input * stream. */ - protected class SocketInputBuffer + protected class SocketInputBuffer implements InputBuffer { /** * Read bytes into the specified chunk. */ @Override - public int doRead(ByteChunk chunk, Request req ) + public int doRead(ByteChunk chunk, Request req ) throws IOException { if (pos >= lastValid) {
java/org/apache/coyote/http11/LocalStrings.properties+3 −1 modified@@ -42,8 +42,10 @@ http11Processor.upgrade=An internal error has occurred as upgraded connections s iib.apr.sslGeneralError=An APR general error was returned by the SSL read operation on APR/native socket [{0}] with wrapper [{1}]. It will be treated as EAGAIN and the socket returned to the poller. iib.eof.error=Unexpected EOF read on the socket -iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 2616 and has been ignored. +iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 7230 and has been ignored. iib.invalidmethod=Invalid character found in method name. HTTP method names must be tokens +iib.invalidRequestTarget=Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 +iib.invalidHttpProtocol=Invalid character found in the HTTP protocol iib.parseheaders.ise.error=Unexpected state: headers already parsed. Buffer not recycled? iib.requestheadertoolarge.error=Request header is too large
java/org/apache/tomcat/util/http/parser/HttpParser.java+43 −1 modified@@ -59,6 +59,8 @@ public class HttpParser { private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; private static final boolean[] IS_HEX = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_NOT_REQUEST_TARGET = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_HTTP_PROTOCOL = new boolean[ARRAY_SIZE]; static { // Digest field types. @@ -103,6 +105,21 @@ public class HttpParser { if ((i >= '0' && i <='9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) { IS_HEX[i] = true; } + + // Not valid for request target. + // Combination of multiple rules from RFC7230 and RFC 3986. Must be + // ASCII, no controls plus a few additional characters excluded + if (IS_CONTROL[i] || i > 127 || + i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' || + i == '^' || i == '`' || i == '{' || i == '|' || i == '}') { + IS_NOT_REQUEST_TARGET[i] = true; + } + + // Not valid for HTTP protocol + // "HTTP/" DIGIT "." DIGIT + if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) { + IS_HTTP_PROTOCOL[i] = true; + } } } @@ -266,6 +283,7 @@ public static String unquote(String input) { return result.toString(); } + public static boolean isToken(int c) { // Fast for correct values, slower for incorrect ones try { @@ -275,15 +293,39 @@ public static boolean isToken(int c) { } } + public static boolean isHex(int c) { - // Fast for correct values, slower for incorrect ones + // Fast for correct values, slower for some incorrect ones try { return IS_HEX[c]; } catch (ArrayIndexOutOfBoundsException ex) { return false; } } + + public static boolean isNotRequestTarget(int c) { + // Fast for valid request target characters, slower for some incorrect + // ones + try { + return IS_NOT_REQUEST_TARGET[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return true; + } + } + + + public static boolean isHttpProtocol(int c) { + // Fast for valid HTTP protocol characters, slower for some incorrect + // ones + try { + return IS_HTTP_PROTOCOL[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return false; + } + } + + // Skip any LWS and return the next char private static int skipLws(StringReader input, boolean withReset) throws IOException {
webapps/docs/changelog.xml+4 −0 modified@@ -122,6 +122,10 @@ Improve detection of I/O errors during async processing on non-container threads and trigger async error handling when they are detected. (markt) </fix> + <add> + Add additional checks for valid characters to the HTTP request line + parsing so invalid request lines are rejected sooner. (markt) + </add> </changelog> </subsection> <subsection name="Web applications">
779d5d34e68eAdd additional checks for valid characters to the HTTP request line
8 files changed · +109 −99
java/org/apache/coyote/http11/AbstractInputBuffer.java+1 −53 modified@@ -30,62 +30,10 @@ public abstract class AbstractInputBuffer<S> implements InputBuffer{ - protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128]; - /** * The string manager for this package. */ - protected static final StringManager sm = - StringManager.getManager(Constants.Package); - - - static { - for (int i = 0; i < 128; i++) { - if (i < 32) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == 127) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '(') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ')') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '<') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '>') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '@') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ',') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ';') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ':') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\\') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\"') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '/') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '[') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ']') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '?') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '=') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '{') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '}') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ' ') { - HTTP_TOKEN_CHAR[i] = false; - } else { - HTTP_TOKEN_CHAR[i] = true; - } - } - } + protected static final StringManager sm = StringManager.getManager(Constants.Package); /**
java/org/apache/coyote/http11/AbstractNioInputBuffer.java+9 −5 modified@@ -21,6 +21,7 @@ import org.apache.coyote.Request; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.parser.HttpParser; public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> { @@ -228,7 +229,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } pos++; @@ -276,9 +277,10 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) parsingRequestLineEol = true; space = true; end = pos; - } else if ((buf[pos] == Constants.QUESTION) - && (parsingRequestLineQPos == -1)) { + } else if ((buf[pos] == Constants.QUESTION) && (parsingRequestLineQPos == -1)) { parsingRequestLineQPos = pos; + } else if (HttpParser.isNotRequestTarget(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } pos++; } @@ -315,7 +317,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (parsingRequestLinePhase == 6) { // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // while (!parsingRequestLineEol) { // Read new bytes if needed @@ -330,6 +332,8 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (end == 0) end = pos; parsingRequestLineEol = true; + } else if (!HttpParser.isHttpProtocol(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } pos++; } @@ -470,7 +474,7 @@ private HeaderParseStatus parseHeader() headerData.realPos = pos; headerData.lastSignificantChar = pos; break; - } else if (chr < 0 || !HTTP_TOKEN_CHAR[chr]) { + } else if (!HttpParser.isToken(chr)) { // If a non-token header is detected, skip the line and // ignore the header headerData.lastSignificantChar = pos;
java/org/apache/coyote/http11/InternalAprInputBuffer.java+9 −5 modified@@ -32,6 +32,7 @@ import org.apache.tomcat.jni.Status; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.SocketWrapper; @@ -181,7 +182,7 @@ public boolean parseRequestLine(boolean useAvailableData) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, start, pos - start); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } @@ -232,9 +233,10 @@ public boolean parseRequestLine(boolean useAvailableData) eol = true; space = true; end = pos; - } else if ((buf[pos] == Constants.QUESTION) - && (questionPos == -1)) { + } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { questionPos = pos; + } else if (HttpParser.isNotRequestTarget(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } pos++; @@ -270,7 +272,7 @@ public boolean parseRequestLine(boolean useAvailableData) // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // while (!eol) { @@ -287,6 +289,8 @@ public boolean parseRequestLine(boolean useAvailableData) if (end == 0) end = pos; eol = true; + } else if (!HttpParser.isHttpProtocol(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } pos++; @@ -385,7 +389,7 @@ private boolean parseHeader() if (buf[pos] == Constants.COLON) { colon = true; headerValue = headers.addValue(buf, start, pos - start); - } else if (buf[pos] < 0 || !HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { // If a non-token header is detected, skip the line and // ignore the header skipLine(start);
java/org/apache/coyote/http11/InternalInputBuffer.java+9 −6 modified@@ -28,6 +28,7 @@ import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.SocketWrapper; @@ -142,7 +143,7 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, start, pos - start); - } else if (!HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } @@ -193,9 +194,10 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) eol = true; space = true; end = pos; - } else if ((buf[pos] == Constants.QUESTION) - && (questionPos == -1)) { + } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { questionPos = pos; + } else if (HttpParser.isNotRequestTarget(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } pos++; @@ -230,9 +232,8 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // - while (!eol) { // Read new bytes if needed @@ -247,6 +248,8 @@ public boolean parseRequestLine(boolean useAvailableDataOnly) if (end == 0) end = pos; eol = true; + } else if (!HttpParser.isHttpProtocol(buf[pos])) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } pos++; @@ -345,7 +348,7 @@ private boolean parseHeader() if (buf[pos] == Constants.COLON) { colon = true; headerValue = headers.addValue(buf, start, pos - start); - } else if (buf[pos] < 0 || !HTTP_TOKEN_CHAR[buf[pos]]) { + } else if (!HttpParser.isToken(buf[pos])) { // If a non-token header is detected, skip the line and // ignore the header skipLine(start);
java/org/apache/coyote/http11/LocalStrings.properties+3 −1 modified@@ -33,8 +33,10 @@ iib.available.readFail=A non-blocking read failed while attempting to determine iib.eof.error=Unexpected EOF read on the socket iib.failedread.apr=Read failed with APR/native error code [{0}] iib.filter.npe=You may not add a null filter -iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 2616 and has been ignored. +iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 7230 and has been ignored. iib.invalidmethod=Invalid character found in method name. HTTP method names must be tokens +iib.invalidRequestTarget=Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 +iib.invalidHttpProtocol=Invalid character found in the HTTP protocol iib.parseheaders.ise.error=Unexpected state: headers already parsed. Buffer not recycled? iib.readtimeout=Timeout attempting to read data from the socket iib.requestheadertoolarge.error=Request header is too large
java/org/apache/tomcat/util/http/parser/HttpParser.java+43 −1 modified@@ -40,6 +40,8 @@ public class HttpParser { private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; private static final boolean[] IS_HEX = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_NOT_REQUEST_TARGET = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_HTTP_PROTOCOL = new boolean[ARRAY_SIZE]; static { for (int i = 0; i < ARRAY_SIZE; i++) { @@ -65,6 +67,21 @@ public class HttpParser { if ((i >= '0' && i <='9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) { IS_HEX[i] = true; } + + // Not valid for request target. + // Combination of multiple rules from RFC7230 and RFC 3986. Must be + // ASCII, no controls plus a few additional characters excluded + if (IS_CONTROL[i] || i > 127 || + i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' || + i == '^' || i == '`' || i == '{' || i == '|' || i == '}') { + IS_NOT_REQUEST_TARGET[i] = true; + } + + // Not valid for HTTP protocol + // "HTTP/" DIGIT "." DIGIT + if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) { + IS_HTTP_PROTOCOL[i] = true; + } } } @@ -99,6 +116,7 @@ public static String unquote(String input) { return result.toString(); } + public static boolean isToken(int c) { // Fast for correct values, slower for incorrect ones try { @@ -108,15 +126,39 @@ public static boolean isToken(int c) { } } + public static boolean isHex(int c) { - // Fast for correct values, slower for incorrect ones + // Fast for correct values, slower for some incorrect ones try { return IS_HEX[c]; } catch (ArrayIndexOutOfBoundsException ex) { return false; } } + + public static boolean isNotRequestTarget(int c) { + // Fast for valid request target characters, slower for some incorrect + // ones + try { + return IS_NOT_REQUEST_TARGET[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return true; + } + } + + + public static boolean isHttpProtocol(int c) { + // Fast for valid HTTP protocol characters, slower for some incorrect + // ones + try { + return IS_HTTP_PROTOCOL[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return false; + } + } + + // Skip any LWS and return the next char static int skipLws(StringReader input, boolean withReset) throws IOException {
test/org/apache/catalina/valves/rewrite/TestRewriteValve.java+31 −28 modified@@ -232,7 +232,7 @@ public void testUtf8WithBothQsFlagsRNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1\u00C2\u00A1", "id=\u00C2\u00A1"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -242,7 +242,7 @@ public void testUtf8WithBothQsFlagsRBNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1%C2%A1", "id=%C2%A1"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -279,8 +279,7 @@ public void testUtf8WithBothQsFlagsRNEQSA() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE,QSA]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1\u00C2\u00A1", - "id=\u00C2\u00A1&di=\u00C2\u00AE"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -290,8 +289,7 @@ public void testUtf8WithBothQsFlagsRBNEQSA() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE,QSA]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1%C2%A1", - "id=%C2%A1&di=\u00C2\u00AE"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -333,7 +331,7 @@ public void testUtf8WithOriginalQsFlagsRNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", - "/b/%C2%A1?id=%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1", "id=\u00C2\u00A1"); + "/b/%C2%A1?id=%C2%A1", null); } @@ -343,7 +341,7 @@ public void testUtf8WithOriginalQsFlagsRBNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]", - "/b/%C2%A1?id=%C2%A1", "/c/\u00C2\u00A1%C2%A1", "id=\u00C2\u00A1"); + "/b/%C2%A1?id=%C2%A1", null); } @@ -394,7 +392,7 @@ public void testUtf8WithRewriteQsFlagsRNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]", - "/b/%C2%A1/id=%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1", "id=\u00C2\u00A1"); + "/b/%C2%A1/id=%C2%A1", null); } @@ -404,7 +402,7 @@ public void testUtf8WithRewriteQsFlagsRBNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]", - "/b/%C2%A1/id=%C2%A1", "/c/\u00C2\u00A1%C2%A1", "id=%C2%A1"); + "/b/%C2%A1/id=%C2%A1", null); } @@ -450,8 +448,7 @@ public void testUtf8FlagsRNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 - doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", - "/b/%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1"); + doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", "/b/%C2%A1", null); } @@ -460,8 +457,7 @@ public void testUtf8FlagsRBNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 - doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]", - "/b/%C2%A1", "/c/\u00C2\u00A1%C2%A1"); + doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]", "/b/%C2%A1", null); } @@ -515,22 +511,29 @@ private void doTestRewrite(String config, String request, String expectedURI, tomcat.start(); - ByteChunk res = getUrl("http://localhost:" + getPort() + request); + ByteChunk res = new ByteChunk(); + int rc = getUrl("http://localhost:" + getPort() + request, res, null); res.setCharset(StandardCharsets.UTF_8); - String body = res.toString(); - RequestDescriptor requestDesc = SnoopResult.parse(body); - String requestURI = requestDesc.getRequestInfo("REQUEST-URI"); - Assert.assertEquals(expectedURI, requestURI); - - if (expectedQueryString != null) { - String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING"); - Assert.assertEquals(expectedQueryString, queryString); - } - - if (expectedAttributeValue != null) { - String attributeValue = requestDesc.getAttribute("X-Test"); - Assert.assertEquals(expectedAttributeValue, attributeValue); + if (expectedURI == null) { + // Rewrite is expected to fail. Probably because invalid characters + // were written into the request target + Assert.assertEquals(400, rc); + } else { + String body = res.toString(); + RequestDescriptor requestDesc = SnoopResult.parse(body); + String requestURI = requestDesc.getRequestInfo("REQUEST-URI"); + Assert.assertEquals(expectedURI, requestURI); + + if (expectedQueryString != null) { + String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING"); + Assert.assertEquals(expectedQueryString, queryString); + } + + if (expectedAttributeValue != null) { + String attributeValue = requestDesc.getAttribute("X-Test"); + Assert.assertEquals(expectedAttributeValue, attributeValue); + } } } }
webapps/docs/changelog.xml+4 −0 modified@@ -97,6 +97,10 @@ Improve detection of I/O errors during async processing on non-container threads and trigger async error handling when they are detected. (markt) </fix> + <add> + Add additional checks for valid characters to the HTTP request line + parsing so invalid request lines are rejected sooner. (markt) + </add> </changelog> </subsection> <subsection name="Web applications">
f96f5751d418Add additional checks for valid characters to the HTTP request line
5 files changed · +89 −83
java/org/apache/coyote/http11/Http11InputBuffer.java+8 −53 modified@@ -28,6 +28,7 @@ import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.MimeHeaders; +import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.ApplicationBufferHandler; import org.apache.tomcat.util.net.SocketWrapperBase; import org.apache.tomcat.util.res.StringManager; @@ -48,56 +49,6 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler private static final StringManager sm = StringManager.getManager(Http11InputBuffer.class); - private static final boolean[] HTTP_TOKEN_CHAR = new boolean[128]; - static { - for (int i = 0; i < 128; i++) { - if (i < 32) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == 127) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '(') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ')') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '<') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '>') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '@') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ',') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ';') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ':') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\\') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\"') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '/') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '[') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ']') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '?') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '=') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '{') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '}') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ' ') { - HTTP_TOKEN_CHAR[i] = false; - } else { - HTTP_TOKEN_CHAR[i] = true; - } - } - } - - private static final byte[] CLIENT_PREFACE_START = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1); @@ -461,7 +412,7 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { space = true; request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart); - } else if (!HTTP_TOKEN_CHAR[chr]) { + } else if (!HttpParser.isToken(chr)) { byteBuffer.position(byteBuffer.position() - 1); throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } @@ -512,6 +463,8 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { end = pos; } else if (chr == Constants.QUESTION && parsingRequestLineQPos == -1) { parsingRequestLineQPos = pos; + } else if (HttpParser.isNotRequestTarget(chr)) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } } if (parsingRequestLineQPos >= 0) { @@ -549,7 +502,7 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { if (parsingRequestLinePhase == 6) { // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // while (!parsingRequestLineEol) { // Read new bytes if needed @@ -567,6 +520,8 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { end = pos; } parsingRequestLineEol = true; + } else if (!HttpParser.isHttpProtocol(chr)) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } } @@ -831,7 +786,7 @@ private HeaderParseStatus parseHeader() throws IOException { headerData.realPos = pos; headerData.lastSignificantChar = pos; break; - } else if (chr < 0 || !HTTP_TOKEN_CHAR[chr]) { + } else if (!HttpParser.isToken(chr)) { // If a non-token header is detected, skip the line and // ignore the header headerData.lastSignificantChar = pos;
java/org/apache/coyote/http11/LocalStrings.properties+3 −1 modified@@ -31,8 +31,10 @@ iib.available.readFail=A non-blocking read failed while attempting to determine iib.eof.error=Unexpected EOF read on the socket iib.failedread.apr=Read failed with APR/native error code [{0}] iib.filter.npe=You may not add a null filter -iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 2616 and has been ignored. +iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 7230 and has been ignored. iib.invalidmethod=Invalid character found in method name. HTTP method names must be tokens +iib.invalidRequestTarget=Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 +iib.invalidHttpProtocol=Invalid character found in the HTTP protocol iib.parseheaders.ise.error=Unexpected state: headers already parsed. Buffer not recycled? iib.readtimeout=Timeout attempting to read data from the socket iib.requestheadertoolarge.error=Request header is too large
java/org/apache/tomcat/util/http/parser/HttpParser.java+43 −1 modified@@ -40,6 +40,8 @@ public class HttpParser { private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; private static final boolean[] IS_HEX = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_NOT_REQUEST_TARGET = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_HTTP_PROTOCOL = new boolean[ARRAY_SIZE]; static { for (int i = 0; i < ARRAY_SIZE; i++) { @@ -65,6 +67,21 @@ public class HttpParser { if ((i >= '0' && i <='9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) { IS_HEX[i] = true; } + + // Not valid for request target. + // Combination of multiple rules from RFC7230 and RFC 3986. Must be + // ASCII, no controls plus a few additional characters excluded + if (IS_CONTROL[i] || i > 127 || + i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' || + i == '^' || i == '`' || i == '{' || i == '|' || i == '}') { + IS_NOT_REQUEST_TARGET[i] = true; + } + + // Not valid for HTTP protocol + // "HTTP/" DIGIT "." DIGIT + if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) { + IS_HTTP_PROTOCOL[i] = true; + } } } @@ -99,6 +116,7 @@ public static String unquote(String input) { return result.toString(); } + public static boolean isToken(int c) { // Fast for correct values, slower for incorrect ones try { @@ -108,15 +126,39 @@ public static boolean isToken(int c) { } } + public static boolean isHex(int c) { - // Fast for correct values, slower for incorrect ones + // Fast for correct values, slower for some incorrect ones try { return IS_HEX[c]; } catch (ArrayIndexOutOfBoundsException ex) { return false; } } + + public static boolean isNotRequestTarget(int c) { + // Fast for valid request target characters, slower for some incorrect + // ones + try { + return IS_NOT_REQUEST_TARGET[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return true; + } + } + + + public static boolean isHttpProtocol(int c) { + // Fast for valid HTTP protocol characters, slower for some incorrect + // ones + try { + return IS_HTTP_PROTOCOL[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return false; + } + } + + // Skip any LWS and return the next char static int skipLws(StringReader input, boolean withReset) throws IOException {
test/org/apache/catalina/valves/rewrite/TestRewriteValve.java+31 −28 modified@@ -232,7 +232,7 @@ public void testUtf8WithBothQsFlagsRNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1\u00C2\u00A1", "id=\u00C2\u00A1"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -242,7 +242,7 @@ public void testUtf8WithBothQsFlagsRBNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1%C2%A1", "id=%C2%A1"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -279,8 +279,7 @@ public void testUtf8WithBothQsFlagsRNEQSA() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE,QSA]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1\u00C2\u00A1", - "id=\u00C2\u00A1&di=\u00C2\u00AE"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -290,8 +289,7 @@ public void testUtf8WithBothQsFlagsRBNEQSA() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE,QSA]", - "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1%C2%A1", - "id=%C2%A1&di=\u00C2\u00AE"); + "/b/%C2%A1/id=%C2%A1?di=%C2%AE", null); } @@ -333,7 +331,7 @@ public void testUtf8WithOriginalQsFlagsRNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", - "/b/%C2%A1?id=%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1", "id=\u00C2\u00A1"); + "/b/%C2%A1?id=%C2%A1", null); } @@ -343,7 +341,7 @@ public void testUtf8WithOriginalQsFlagsRBNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]", - "/b/%C2%A1?id=%C2%A1", "/c/\u00C2\u00A1%C2%A1", "id=\u00C2\u00A1"); + "/b/%C2%A1?id=%C2%A1", null); } @@ -394,7 +392,7 @@ public void testUtf8WithRewriteQsFlagsRNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]", - "/b/%C2%A1/id=%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1", "id=\u00C2\u00A1"); + "/b/%C2%A1/id=%C2%A1", null); } @@ -404,7 +402,7 @@ public void testUtf8WithRewriteQsFlagsRBNE() throws Exception { // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]", - "/b/%C2%A1/id=%C2%A1", "/c/\u00C2\u00A1%C2%A1", "id=%C2%A1"); + "/b/%C2%A1/id=%C2%A1", null); } @@ -450,8 +448,7 @@ public void testUtf8FlagsRNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 - doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", - "/b/%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1"); + doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]", "/b/%C2%A1", null); } @@ -460,8 +457,7 @@ public void testUtf8FlagsRBNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location // header which will be treated as if they are ISO-8859-1 - doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]", - "/b/%C2%A1", "/c/\u00C2\u00A1%C2%A1"); + doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]", "/b/%C2%A1", null); } @@ -515,22 +511,29 @@ private void doTestRewrite(String config, String request, String expectedURI, tomcat.start(); - ByteChunk res = getUrl("http://localhost:" + getPort() + request); + ByteChunk res = new ByteChunk(); + int rc = getUrl("http://localhost:" + getPort() + request, res, null); res.setCharset(StandardCharsets.UTF_8); - String body = res.toString(); - RequestDescriptor requestDesc = SnoopResult.parse(body); - String requestURI = requestDesc.getRequestInfo("REQUEST-URI"); - Assert.assertEquals(expectedURI, requestURI); - - if (expectedQueryString != null) { - String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING"); - Assert.assertEquals(expectedQueryString, queryString); - } - - if (expectedAttributeValue != null) { - String attributeValue = requestDesc.getAttribute("X-Test"); - Assert.assertEquals(expectedAttributeValue, attributeValue); + if (expectedURI == null) { + // Rewrite is expected to fail. Probably because invalid characters + // were written into the request target + Assert.assertEquals(400, rc); + } else { + String body = res.toString(); + RequestDescriptor requestDesc = SnoopResult.parse(body); + String requestURI = requestDesc.getRequestInfo("REQUEST-URI"); + Assert.assertEquals(expectedURI, requestURI); + + if (expectedQueryString != null) { + String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING"); + Assert.assertEquals(expectedQueryString, queryString); + } + + if (expectedAttributeValue != null) { + String attributeValue = requestDesc.getAttribute("X-Test"); + Assert.assertEquals(expectedAttributeValue, attributeValue); + } } } }
webapps/docs/changelog.xml+4 −0 modified@@ -151,6 +151,10 @@ Improve detection of I/O errors during async processing on non-container threads and trigger async error handling when they are detected. (markt) </fix> + <add> + Add additional checks for valid characters to the HTTP request line + parsing so invalid request lines are rejected sooner. (markt) + </add> </changelog> </subsection> <subsection name="Jasper">
516bda676ac8Add additional checks for valid characters to the HTTP request line
5 files changed · +69 −55
java/org/apache/coyote/http11/Http11InputBuffer.java+8 −53 modified@@ -27,6 +27,7 @@ import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.MimeHeaders; +import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.ApplicationBufferHandler; import org.apache.tomcat.util.net.SocketWrapperBase; import org.apache.tomcat.util.res.StringManager; @@ -47,56 +48,6 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler private static final StringManager sm = StringManager.getManager(Http11InputBuffer.class); - private static final boolean[] HTTP_TOKEN_CHAR = new boolean[128]; - static { - for (int i = 0; i < 128; i++) { - if (i < 32) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == 127) { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '(') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ')') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '<') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '>') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '@') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ',') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ';') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ':') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\\') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '\"') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '/') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '[') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ']') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '?') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '=') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '{') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == '}') { - HTTP_TOKEN_CHAR[i] = false; - } else if (i == ' ') { - HTTP_TOKEN_CHAR[i] = false; - } else { - HTTP_TOKEN_CHAR[i] = true; - } - } - } - - private static final byte[] CLIENT_PREFACE_START = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1); @@ -446,7 +397,7 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { space = true; request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart); - } else if (!HTTP_TOKEN_CHAR[chr]) { + } else if (!HttpParser.isToken(chr)) { byteBuffer.position(byteBuffer.position() - 1); throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } @@ -497,6 +448,8 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { end = pos; } else if (chr == Constants.QUESTION && parsingRequestLineQPos == -1) { parsingRequestLineQPos = pos; + } else if (HttpParser.isNotRequestTarget(chr)) { + throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } } if (parsingRequestLineQPos >= 0) { @@ -534,7 +487,7 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { if (parsingRequestLinePhase == 6) { // // Reading the protocol - // Protocol is always US-ASCII + // Protocol is always "HTTP/" DIGIT "." DIGIT // while (!parsingRequestLineEol) { // Read new bytes if needed @@ -552,6 +505,8 @@ boolean parseRequestLine(boolean keptAlive) throws IOException { end = pos; } parsingRequestLineEol = true; + } else if (!HttpParser.isHttpProtocol(chr)) { + throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } } @@ -816,7 +771,7 @@ private HeaderParseStatus parseHeader() throws IOException { headerData.realPos = pos; headerData.lastSignificantChar = pos; break; - } else if (chr < 0 || !HTTP_TOKEN_CHAR[chr]) { + } else if (chr < 0 || !HttpParser.isToken(chr)) { // If a non-token header is detected, skip the line and // ignore the header headerData.lastSignificantChar = pos;
java/org/apache/coyote/http11/LocalStrings.properties+3 −1 modified@@ -31,8 +31,10 @@ iib.available.readFail=A non-blocking read failed while attempting to determine iib.eof.error=Unexpected EOF read on the socket iib.failedread.apr=Read failed with APR/native error code [{0}] iib.filter.npe=You may not add a null filter -iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 2616 and has been ignored. +iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 7230 and has been ignored. iib.invalidmethod=Invalid character found in method name. HTTP method names must be tokens +iib.invalidRequestTarget=Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986 +iib.invalidHttpProtocol=Invalid character found in the HTTP protocol iib.parseheaders.ise.error=Unexpected state: headers already parsed. Buffer not recycled? iib.readtimeout=Timeout attempting to read data from the socket iib.requestheadertoolarge.error=Request header is too large
java/org/apache/tomcat/util/http/parser/HttpParser.java+43 −1 modified@@ -40,6 +40,8 @@ public class HttpParser { private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; private static final boolean[] IS_HEX = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_NOT_REQUEST_TARGET = new boolean[ARRAY_SIZE]; + private static final boolean[] IS_HTTP_PROTOCOL = new boolean[ARRAY_SIZE]; static { for (int i = 0; i < ARRAY_SIZE; i++) { @@ -65,6 +67,21 @@ public class HttpParser { if ((i >= '0' && i <='9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) { IS_HEX[i] = true; } + + // Not valid for request target. + // Combination of multiple rules from RFC7230 and RFC 3986. Must be + // ASCII, no controls plus a few additional characters excluded + if (IS_CONTROL[i] || i > 127 || + i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' || + i == '^' || i == '`' || i == '{' || i == '|' || i == '}') { + IS_NOT_REQUEST_TARGET[i] = true; + } + + // Not valid for HTTP protocol + // "HTTP/" DIGIT "." DIGIT + if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) { + IS_HTTP_PROTOCOL[i] = true; + } } } @@ -99,6 +116,7 @@ public static String unquote(String input) { return result.toString(); } + public static boolean isToken(int c) { // Fast for correct values, slower for incorrect ones try { @@ -108,15 +126,39 @@ public static boolean isToken(int c) { } } + public static boolean isHex(int c) { - // Fast for correct values, slower for incorrect ones + // Fast for correct values, slower for some incorrect ones try { return IS_HEX[c]; } catch (ArrayIndexOutOfBoundsException ex) { return false; } } + + public static boolean isNotRequestTarget(int c) { + // Fast for valid request target characters, slower for some incorrect + // ones + try { + return IS_NOT_REQUEST_TARGET[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return true; + } + } + + + public static boolean isHttpProtocol(int c) { + // Fast for valid HTTP protocol characters, slower for some incorrect + // ones + try { + return IS_HTTP_PROTOCOL[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return false; + } + } + + // Skip any LWS and return the next char static int skipLws(StringReader input, boolean withReset) throws IOException {
test/org/apache/catalina/valves/rewrite/TestRewriteValve.java+11 −0 modified@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.apache.catalina.Context; @@ -227,6 +228,7 @@ public void testUtf8WithBothQsFlagsRB() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithBothQsFlagsRNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -237,6 +239,7 @@ public void testUtf8WithBothQsFlagsRNE() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithBothQsFlagsRBNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -274,6 +277,7 @@ public void testUtf8WithBothQsFlagsRBQSA() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithBothQsFlagsRNEQSA() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -285,6 +289,7 @@ public void testUtf8WithBothQsFlagsRNEQSA() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithBothQsFlagsRBNEQSA() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -328,6 +333,7 @@ public void testUtf8WithOriginalQsFlagsRB() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithOriginalQsFlagsRNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -338,6 +344,7 @@ public void testUtf8WithOriginalQsFlagsRNE() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithOriginalQsFlagsRBNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -389,6 +396,7 @@ public void testUtf8WithRewriteQsFlagsRB() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithRewriteQsFlagsRNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -399,6 +407,7 @@ public void testUtf8WithRewriteQsFlagsRNE() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8WithRewriteQsFlagsRBNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -446,6 +455,7 @@ public void testUtf8FlagsRB() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8FlagsRNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location @@ -456,6 +466,7 @@ public void testUtf8FlagsRNE() throws Exception { @Test + @Ignore // Use of NE results in invalid characters in request-target public void testUtf8FlagsRBNE() throws Exception { // Note %C2%A1 == \u00A1 // Failing to escape the redirect means UTF-8 bytes in the Location
webapps/docs/changelog.xml+4 −0 modified@@ -174,6 +174,10 @@ Improve detection of I/O errors during async processing on non-container threads and trigger async error handling when they are detected. (markt) </fix> + <add> + Add additional checks for valid characters to the HTTP request line + parsing so invalid request lines are rejected sooner. (markt) + </add> </changelog> </subsection> <subsection name="Jasper">
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
66- www.securityfocus.com/bid/94461nvdThird Party AdvisoryVDB Entry
- github.com/advisories/GHSA-jc7p-5r39-9477ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-6816ghsaADVISORY
- tomcat.apache.org/security-6.htmlnvdRelease NotesVendor AdvisoryWEB
- tomcat.apache.org/security-7.htmlnvdRelease NotesVendor AdvisoryWEB
- tomcat.apache.org/security-8.htmlnvdRelease NotesVendor AdvisoryWEB
- tomcat.apache.org/security-8.htmlnvdRelease NotesVendor AdvisoryWEB
- tomcat.apache.org/security-9.htmlnvdRelease NotesVendor AdvisoryWEB
- rhn.redhat.com/errata/RHSA-2017-0244.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2017-0245.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2017-0246.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2017-0247.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2017-0250.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2017-0457.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2017-0527.htmlnvdWEB
- www.debian.org/security/2016/dsa-3738nvdWEB
- www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.htmlnvdWEB
- access.redhat.com/errata/RHSA-2017:0455nvdWEB
- access.redhat.com/errata/RHSA-2017:0456nvdWEB
- access.redhat.com/errata/RHSA-2017:0935nvdWEB
- github.com/apache/tomcat/commit/516bda676ac8d0284da3e0295a7df70391315360ghsaWEB
- github.com/apache/tomcat/commit/cdc0a935c2173aff60039a0b85e57a461381107cghsaWEB
- github.com/apache/tomcat/commit/f96f5751d418ae5a2f550be040daf9c5f7d99256ghsaWEB
- github.com/apache/tomcat80/commit/779d5d34e68e50d2f721897050b147106992f566ghsaWEB
- lists.apache.org/thread.html/343558d982879bf88ec20dbf707f8c11255f8e219e81d45c4f8d0551%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/343558d982879bf88ec20dbf707f8c11255f8e219e81d45c4f8d0551@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/37220405a377c0182d2afdbc36461c4783b2930fbeae3a17f1333113%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/37220405a377c0182d2afdbc36461c4783b2930fbeae3a17f1333113@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/388a323769f1dff84c9ec905455aa73fbcb20338e3c7eb131457f708%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/388a323769f1dff84c9ec905455aa73fbcb20338e3c7eb131457f708@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/39ae1f0bd5867c15755a6f959b271ade1aea04ccdc3b2e639dcd903b%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/39ae1f0bd5867c15755a6f959b271ade1aea04ccdc3b2e639dcd903b@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/3d19773b4cf0377db62d1e9328bf9160bf1819f04f988315086931d7%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/3d19773b4cf0377db62d1e9328bf9160bf1819f04f988315086931d7@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/6af47120905aa7d8fe12f42e8ff2284fb338ba141d3b77b8c7cb61b3%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/6af47120905aa7d8fe12f42e8ff2284fb338ba141d3b77b8c7cb61b3@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/845312a10aabbe2c499fca94003881d2c79fc993d85f34c1f5c77424%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/845312a10aabbe2c499fca94003881d2c79fc993d85f34c1f5c77424@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/88855876c33f2f9c532ffb75bfee570ccf0b17ffa77493745af9a17a%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/88855876c33f2f9c532ffb75bfee570ccf0b17ffa77493745af9a17a@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/b5e3f51d28cd5d9b1809f56594f2cf63dcd6a90429e16ea9f83bbedc%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/b5e3f51d28cd5d9b1809f56594f2cf63dcd6a90429e16ea9f83bbedc@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/b84ad1258a89de5c9c853c7f2d3ad77e5b8b2930be9e132d5cef6b95%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/b84ad1258a89de5c9c853c7f2d3ad77e5b8b2930be9e132d5cef6b95@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/b8a1bf18155b552dcf9a928ba808cbadad84c236d85eab3033662cfb%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/b8a1bf18155b552dcf9a928ba808cbadad84c236d85eab3033662cfb@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r03c597a64de790ba42c167efacfa23300c3d6c9fe589ab87fe02859c%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/r03c597a64de790ba42c167efacfa23300c3d6c9fe589ab87fe02859c@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r587e50b86c1a96ee301f751d50294072d142fd6dc08a8987ae9f3a9b%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/r587e50b86c1a96ee301f751d50294072d142fd6dc08a8987ae9f3a9b@%3Cdev.tomcat.apache.org%3EghsaWEB
- lists.apache.org/thread.html/r9136ff5b13e4f1941360b5a309efee2c114a14855578c3a2cbe5d19c%40%3Cdev.tomcat.apache.org%3EnvdWEB
- lists.apache.org/thread.html/r9136ff5b13e4f1941360b5a309efee2c114a14855578c3a2cbe5d19c@%3Cdev.tomcat.apache.org%3EghsaWEB
- security.netapp.com/advisory/ntap-20180607-0001ghsaWEB
- svn.apache.org/viewvcghsaWEB
- svn.apache.org/viewvcghsaWEB
- svn.apache.org/viewvcghsaWEB
- svn.apache.org/viewvcghsaWEB
- svn.apache.org/viewvcghsaWEB
- usn.ubuntu.com/4557-1ghsaWEB
- web.archive.org/web/20161204121236/http://www.securityfocus.com/bid/94461ghsaWEB
- web.archive.org/web/20170929085438/http://www.securitytracker.com/id/1037332ghsaWEB
- www.exploit-db.com/exploits/41783ghsaWEB
- www.securitytracker.com/id/1037332nvd
- security.netapp.com/advisory/ntap-20180607-0001/nvd
- usn.ubuntu.com/4557-1/nvd
- www.exploit-db.com/exploits/41783/nvd
News mentions
0No linked articles in our index yet.