This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 7095d1b9c8910c3bb647f0c496eba13b3aa7410a Author: Mark Thomas <ma...@apache.org> AuthorDate: Thu Oct 14 13:44:52 2021 +0100 Add option to reject "suspicious" URIs --- java/org/apache/catalina/connector/Connector.java | 13 ++++ .../apache/catalina/connector/CoyoteAdapter.java | 91 ++++++++++++++++++++++ webapps/docs/changelog.xml | 7 +- webapps/docs/config/http.xml | 6 ++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/java/org/apache/catalina/connector/Connector.java b/java/org/apache/catalina/connector/Connector.java index 235aa1c..1284d87 100644 --- a/java/org/apache/catalina/connector/Connector.java +++ b/java/org/apache/catalina/connector/Connector.java @@ -296,6 +296,9 @@ public class Connector extends LifecycleMBeanBase { protected boolean useBodyEncodingForURI = false; + private boolean rejectSuspiciousURIs; + + // ------------------------------------------------------------- Properties /** @@ -914,6 +917,16 @@ public class Connector extends LifecycleMBeanBase { } + public boolean getRejectSuspiciousURIs() { + return rejectSuspiciousURIs; + } + + + public void setRejectSuspiciousURIs(boolean rejectSuspiciousURIs) { + this.rejectSuspiciousURIs = rejectSuspiciousURIs; + } + + // --------------------------------------------------------- Public Methods /** diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java index f1db80f..771cc50 100644 --- a/java/org/apache/catalina/connector/CoyoteAdapter.java +++ b/java/org/apache/catalina/connector/CoyoteAdapter.java @@ -46,6 +46,7 @@ import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.B2CConverter; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.CharChunk; +import org.apache.tomcat.util.buf.HexUtils; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.ServerCookie; import org.apache.tomcat.util.http.ServerCookies; @@ -631,6 +632,12 @@ public class CoyoteAdapter implements Adapter { MessageBytes decodedURI = req.decodedURI(); if (undecodedURI.getType() == MessageBytes.T_BYTES) { + if (connector.getRejectSuspiciousURIs()) { + if (checkSuspiciousURIs(undecodedURI.getByteChunk())) { + response.sendError(400, "Invalid URI"); + } + } + // Copy the raw URI to the decodedURI decodedURI.duplicate(undecodedURI); @@ -661,6 +668,8 @@ public class CoyoteAdapter implements Adapter { * non-normalized URI * - req.decodedURI() has been set to the decoded, normalized form * of req.requestURI() + * - 'suspicious' URI filtering - if required - has already been + * performed */ decodedURI.toChars(); // Remove all path parameters; any needed path parameter should be set @@ -1268,4 +1277,86 @@ public class CoyoteAdapter implements Adapter { protected static void copyBytes(byte[] b, int dest, int src, int len) { System.arraycopy(b, src, b, dest, len); } + + + /* + * Examine URI segment by segment for 'suspicious' URIs. + */ + private static boolean checkSuspiciousURIs(ByteChunk undecodedURI) { + byte[] bytes = undecodedURI.getBytes(); + int start = undecodedURI.getStart(); + int end = undecodedURI.getEnd(); + int segmentStart = -1; + int segmentEnd = -1; + + // Find first segment + segmentStart = undecodedURI.indexOf('/', 0); + if (segmentStart > -1) { + segmentEnd = undecodedURI.indexOf('/', segmentStart + 1); + } + + while (segmentStart > -1) { + int pos = start + segmentStart + 1; + + // Empty segment other than final segment with path parameters + if (segmentEnd > 0 && bytes[pos] == ';') { + return true; + } + + // encoded dot-segments and/or dot-segments with path parameters + int dotCount = 0; + boolean encodedDot = false; + while (pos < end) { + if (bytes[pos] == '.') { + dotCount++; + pos++; + } else if (pos + 2 < end && bytes[pos] == '%' && bytes[pos + 1] == '2' && (bytes[pos+2] == 'e' || bytes[pos+2] == 'E')) { + encodedDot = true; + dotCount++; + pos += 3; + } else if (bytes[pos] == ';') { + if (dotCount > 0) { + return true; + } + break; + } else if (bytes[pos] == '/') { + break; + } else { + dotCount = 0; + break; + } + } + if (dotCount > 0 && encodedDot) { + return true; + } + + // %nn encoded controls or '/' + pos = start + segmentStart + 1; + while (pos < end) { + if (pos + 2 < end && bytes[pos] == '%') { + byte b1 = bytes[pos + 1]; + byte b2 = bytes[pos + 2]; + pos += 3; + int decoded = (HexUtils.getDec(b1) << 4) + HexUtils.getDec(b2); + if (decoded < 20 || decoded == 0x7F || decoded == 0x2F) { + return true; + } + } else { + pos++; + } + } + + // Move to next segment + if (segmentEnd == -1) { + segmentStart = -1; + } else { + segmentStart = segmentEnd; + if (segmentStart > -1) { + segmentEnd = undecodedURI.indexOf('/', segmentStart + 1); + } + } + } + + return false; + } } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index abdcfdf..ab48789 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -131,8 +131,13 @@ not valid for the given URI encoding now trigger a 400 response. (markt) </fix> <fix> - Ensure that a requets URI must start with a <code>/</code>. (markt) + Ensure that a request URI starts with a <code>/</code>. (markt) </fix> + <add> + Add a new Connector option, <code>rejectSuspiciousURIs</code> that will + causes 'suspicious' (see the Servlet 6.0 specification) URIs to be + rejected with a 400 response. (markt) + </add> </changelog> </subsection> <subsection name="Coyote"> diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml index 9a800ed..f6e8d08 100644 --- a/webapps/docs/config/http.xml +++ b/webapps/docs/config/http.xml @@ -261,6 +261,12 @@ number specified here.</p> </attribute> + <attribute name="rejectSuspiciousURIs" required="false"> + <p>Should this <strong>Connector</strong> reject a requests if the URI + matches one of the suspicious URIs patterns identified by the Servlet 6.0 + specification? The default value is <code>false</code>.</p> + </attribute> + <attribute name="scheme" required="false"> <p>Set this attribute to the name of the protocol you wish to have returned by calls to <code>request.getScheme()</code>. For --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org