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

Reply via email to