This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new 0517433f47 Reject WebSocket handshakes with repeated or invalid
version and key (#1113)
0517433f47 is described below
commit 0517433f47f559c394fc01568eb99f3d16a51847
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]