This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/11.0.x by this push:
     new 053ced8c5c Reject WebSocket handshakes with repeated or invalid 
version and key (#1113)
053ced8c5c is described below

commit 053ced8c5cb3dd643757552bbe383918f9c13afc
Author: Arturo Bernal <[email protected]>
AuthorDate: Thu Nov 5 18:45:02 2026 +0100

    Reject WebSocket handshakes with repeated or invalid version and key (#1113)
    
    RFC 6455, sections 11.3.1 and 11.3.5 do not allow the Sec-WebSocket-Key
    and Sec-WebSocket-Version header fields to appear more than once in the
    opening handshake request, and section 4.2.1 requires the version to be
    13. A request with repeated fields, or with a version list such as
    "8, 13", was accepted. Reject repeated fields with a 400 response and a
    version other than 13 with a 426 response.
---
 .../tomcat/websocket/server/UpgradeUtil.java       | 14 +++-
 .../server/TestUpgradeSingleValueHeaders.java      | 93 ++++++++++++++++++++++
 2 files changed, 106 insertions(+), 1 deletion(-)

diff --git a/java/org/apache/tomcat/websocket/server/UpgradeUtil.java 
b/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
index 857e321b04..925717ce7f 100644
--- a/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
+++ b/java/org/apache/tomcat/websocket/server/UpgradeUtil.java
@@ -115,7 +115,14 @@ public class UpgradeUtil {
             resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
             return;
         }
-        if (!headerContainsToken(req, Constants.WS_VERSION_HEADER_NAME, 
Constants.WS_VERSION_HEADER_VALUE)) {
+        // RFC 6455, sections 11.3.1 and 11.3.5: these header fields must not 
appear more than once in a request
+        if (countHeaders(req, Constants.WS_VERSION_HEADER_NAME) > 1 ||
+                countHeaders(req, Constants.WS_KEY_HEADER_NAME) > 1) {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        String version = req.getHeader(Constants.WS_VERSION_HEADER_NAME);
+        if (version == null || 
!Constants.WS_VERSION_HEADER_VALUE.equals(version.trim())) {
             resp.setStatus(HttpServletResponse.SC_UPGRADE_REQUIRED);
             resp.setHeader(Constants.WS_VERSION_HEADER_NAME, 
Constants.WS_VERSION_HEADER_VALUE);
             return;
@@ -330,6 +337,11 @@ public class UpgradeUtil {
     }
 
 
+    private static int countHeaders(HttpServletRequest req, String headerName) 
{
+        return Collections.list(req.getHeaders(headerName)).size();
+    }
+
+
     /*
      * This only works for tokens. Quoted strings need more sophisticated 
parsing.
      */
diff --git 
a/test/org/apache/tomcat/websocket/server/TestUpgradeSingleValueHeaders.java 
b/test/org/apache/tomcat/websocket/server/TestUpgradeSingleValueHeaders.java
new file mode 100644
index 0000000000..9ba687ed5a
--- /dev/null
+++ b/test/org/apache/tomcat/websocket/server/TestUpgradeSingleValueHeaders.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.websocket.server;
+
+import java.nio.charset.StandardCharsets;
+
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.websocket.CloseReason.CloseCodes;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.tomcat.websocket.TesterEchoServer;
+import org.apache.tomcat.websocket.WebSocketBaseTest;
+
+/**
+ * RFC 6455, sections 4.2.1, 11.3.1 and 11.3.5: the Sec-WebSocket-Key and 
Sec-WebSocket-Version header fields must
+ * appear once in the opening handshake request and the version must be 13.
+ */
+public class TestUpgradeSingleValueHeaders extends WebSocketBaseTest {
+
+    private static final String KEY = "Sec-WebSocket-Key: 
MDEyMzQ1Njc4OTAxMjM0NQ==\r\n";
+    private static final String VERSION = "Sec-WebSocket-Version: 13\r\n";
+
+
+    @Test
+    public void testValid() throws Exception {
+        doTest(KEY + VERSION, HttpServletResponse.SC_SWITCHING_PROTOCOLS);
+    }
+
+
+    @Test
+    public void testDuplicateKey() throws Exception {
+        doTest(KEY + KEY + VERSION, HttpServletResponse.SC_BAD_REQUEST);
+    }
+
+
+    @Test
+    public void testDuplicateVersion() throws Exception {
+        doTest(KEY + VERSION + VERSION, HttpServletResponse.SC_BAD_REQUEST);
+    }
+
+
+    @Test
+    public void testVersionList() throws Exception {
+        doTest(KEY + "Sec-WebSocket-Version: 8, 13\r\n", 
HttpServletResponse.SC_UPGRADE_REQUIRED);
+    }
+
+
+    @Test
+    public void testUnsupportedVersion() throws Exception {
+        doTest(KEY + "Sec-WebSocket-Version: 8\r\n", 
HttpServletResponse.SC_UPGRADE_REQUIRED);
+    }
+
+
+    private void doTest(String keyAndVersionHeaders, int expectedStatusCode) 
throws Exception {
+        startServer(TesterEchoServer.Config.class);
+
+        TesterWsClient client = new TesterWsClient("localhost", getPort());
+        // Replace the key and version header lines of the default request
+        String req = 
client.createUpgradeRequest(TesterEchoServer.Config.PATH_BASIC)
+                .replaceAll("Sec-WebSocket-(Key|Version): [^\r]*\r\n", "")
+                .replace("Upgrade: websocket\r\n", "Upgrade: websocket\r\n" + 
keyAndVersionHeaders);
+        client.write(req.getBytes(StandardCharsets.UTF_8));
+        int rc = client.readUpgradeResponse();
+
+        Assert.assertEquals(expectedStatusCode, rc);
+
+        if (expectedStatusCode == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
+            client.sendCloseFrame(CloseCodes.NORMAL_CLOSURE);
+            // Read (and ignore) the response
+            byte[] buf = new byte[256];
+            while (client.read(buf) > 0) {
+                // Ignore
+            }
+        }
+        client.closeSocket();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to