- Revision
- 264306
- Author
- [email protected]
- Date
- 2020-07-13 10:57:59 -0700 (Mon, 13 Jul 2020)
Log Message
Add artificial delay to WebSocket connections to mitigate port scanning attacks
https://bugs.webkit.org/show_bug.cgi?id=213143
<rdar://problem/64308927>
Reviewed by Alex Christensen.
Source/WebKit:
When establishing web socket connections, closed ports typically
return an error more quickly than open ports due to the additional
time it takes open ports to perform a TLS handshake. This patch
adds a delay to the closed-port case to prevent distinguishing these
cases.
Test: http/tests/websocket/tests/hybi/closed-port-delay.html
* NetworkProcess/NetworkSocketStream.cpp:
(WebKit::NetworkSocketStream::NetworkSocketStream):
(WebKit::randomDelay):
Add a random delay between 10 and 100ms before sending IPC.
(WebKit::NetworkSocketStream::sendDelayedFailMessage):
(WebKit::NetworkSocketStream::didFailSocketStream):
Only delay for the proper error code indicating the connection
was refused for a closed port.
* NetworkProcess/NetworkSocketStream.h:
LayoutTests:
Added layout test coverage, making sure closed ports always experience
a delay of at least 10ms.
* TestExpectations:
The added delay code is only present in WebKit, not WebKitLegacy.
Additionally, web sockets behave differently on Windows, so this test
should be skipped on all platforms and only enabled for mac and ios wk2.
* http/tests/websocket/connection-refusal-in-frame-resource-load-statistics-expected.txt:
With the added delay, this test will only receive one console message.
To confirm there will be no flakiness, I ran this test for 500
iterations and ensured they passed.
* http/tests/websocket/tests/hybi/closed-port-delay-expected.txt:
* http/tests/websocket/tests/hybi/closed-port-delay.html: Added.
* platform/wk2/TestExpectations:
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (264305 => 264306)
--- trunk/LayoutTests/ChangeLog 2020-07-13 17:36:21 UTC (rev 264305)
+++ trunk/LayoutTests/ChangeLog 2020-07-13 17:57:59 UTC (rev 264306)
@@ -1,3 +1,27 @@
+2020-07-13 Kate Cheney <[email protected]>
+
+ Add artificial delay to WebSocket connections to mitigate port scanning attacks
+ https://bugs.webkit.org/show_bug.cgi?id=213143
+ <rdar://problem/64308927>
+
+ Reviewed by Alex Christensen.
+
+ Added layout test coverage, making sure closed ports always experience
+ a delay of at least 10ms.
+
+ * TestExpectations:
+ The added delay code is only present in WebKit, not WebKitLegacy.
+ Additionally, web sockets behave differently on Windows, so this test
+ should be skipped on all platforms and only enabled for mac and ios wk2.
+
+ * http/tests/websocket/connection-refusal-in-frame-resource-load-statistics-expected.txt:
+ With the added delay, this test will only receive one console message.
+ To confirm there will be no flakiness, I ran this test for 500
+ iterations and ensured they passed.
+ * http/tests/websocket/tests/hybi/closed-port-delay-expected.txt:
+ * http/tests/websocket/tests/hybi/closed-port-delay.html: Added.
+ * platform/wk2/TestExpectations:
+
2020-07-13 Keith Miller <[email protected]>
Clean up SourceProvider and add caller relative load script to jsc.cpp
Modified: trunk/LayoutTests/TestExpectations (264305 => 264306)
--- trunk/LayoutTests/TestExpectations 2020-07-13 17:36:21 UTC (rev 264305)
+++ trunk/LayoutTests/TestExpectations 2020-07-13 17:57:59 UTC (rev 264306)
@@ -4367,3 +4367,5 @@
# Only Cocoa ports support the -apple-system font keywords
fast/text/text-styles/-apple-system [ ImageOnlyFailure ]
+# Artificial web socket delays are only supported on wk2.
+http/tests/websocket/tests/hybi/closed-port-delay.html [ Skip ]
Modified: trunk/LayoutTests/http/tests/websocket/connection-refusal-in-frame-resource-load-statistics-expected.txt (264305 => 264306)
--- trunk/LayoutTests/http/tests/websocket/connection-refusal-in-frame-resource-load-statistics-expected.txt 2020-07-13 17:36:21 UTC (rev 264305)
+++ trunk/LayoutTests/http/tests/websocket/connection-refusal-in-frame-resource-load-statistics-expected.txt 2020-07-13 17:57:59 UTC (rev 264306)
@@ -1,5 +1,4 @@
CONSOLE MESSAGE: WebSocket network error: The operation couldn’t be completed. Connection refused
-CONSOLE MESSAGE: WebSocket network error: The operation couldn’t be completed. Connection refused
Construct a cross-site WebSocket in a frame with server-side refusal. The test passes if Resource Load Statistics logs it properly.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
Copied: trunk/LayoutTests/http/tests/websocket/tests/hybi/closed-port-delay-expected.txt (from rev 264305, trunk/LayoutTests/http/tests/websocket/connection-refusal-in-frame-resource-load-statistics-expected.txt) (0 => 264306)
--- trunk/LayoutTests/http/tests/websocket/tests/hybi/closed-port-delay-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/websocket/tests/hybi/closed-port-delay-expected.txt 2020-07-13 17:57:59 UTC (rev 264306)
@@ -0,0 +1,13 @@
+CONSOLE MESSAGE: WebSocket network error: The operation couldn’t be completed. Connection refused
+CONSOLE MESSAGE: WebSocket network error: The operation couldn’t be completed. Connection refused
+CONSOLE MESSAGE: WebSocket network error: The operation couldn’t be completed. Connection refused
+Tests that WebSocket close report is delayed to mitigate port scanning attacks.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS Successfully delayed reporting of closed WebSocket connection
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/http/tests/websocket/tests/hybi/closed-port-delay.html (0 => 264306)
--- trunk/LayoutTests/http/tests/websocket/tests/hybi/closed-port-delay.html (rev 0)
+++ trunk/LayoutTests/http/tests/websocket/tests/hybi/closed-port-delay.html 2020-07-13 17:57:59 UTC (rev 264306)
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script>
+description("Tests that WebSocket close report is delayed to mitigate port scanning attacks.");
+
+jsTestIsAsync = true;
+
+var countReceivedMessage = 0;
+
+async function scan_ports()
+{
+ // Scan multiple closed ports.
+ var result1 = await scan_port(8181);
+ var result2 = await scan_port(1234);
+ var result3 = await scan_port(3001);
+ if (result3 == "testPassed" && result2 == "testPassed" && result1 == "testPassed") {
+ testPassed("Successfully delayed reporting of closed WebSocket connection")
+ finishJSTest();
+ } else {
+ testFailed("Failed to delay closed WebSocket connection");
+ finishJSTest();
+ }
+}
+
+// Test based off of BeEF port scanning attack technique (https://github.com/beefproject/beef/blob/master/modules/network/port_scanner/command.js)
+function scan_port(port)
+{
+ return new Promise(function(resolve, reject) {
+ var url = "" + port;
+ var ws = new WebSocket(url);
+ var startTime = (new Date).getTime();
+ var timeout = 3000;
+
+ clearIntervalID = setInterval(
+ function ()
+ {
+ var interval = (new Date).getTime() - startTime;
+
+ if (ws.readyState === WebSocket.CONNECTING)
+ {
+ }
+
+ if (ws.readyState === WebSocket.OPEN)
+ {
+ }
+
+ if (ws.readyState === WebSocket.CLOSING)
+ {
+ }
+
+ if (ws.readyState === WebSocket.CLOSED)
+ {
+ clearInterval(clearIntervalID);
+ ws.close();
+ countReceivedMessage++;
+ if (interval < 10) {
+ debug("Timer failed to delay report of closed state");
+ resolve("testFailed");
+ } else
+ resolve("testPassed");
+ }
+
+ if (interval >= timeout)
+ {
+ clearInterval(clearIntervalID);
+ debug("Connection timeout");
+ ws.close();
+ resolve("testFailed");
+ }
+ }
+ , 1);
+ });
+}
+
+scan_ports();
+
+</script>
+</body>
+</html>
Modified: trunk/LayoutTests/platform/wk2/TestExpectations (264305 => 264306)
--- trunk/LayoutTests/platform/wk2/TestExpectations 2020-07-13 17:36:21 UTC (rev 264305)
+++ trunk/LayoutTests/platform/wk2/TestExpectations 2020-07-13 17:57:59 UTC (rev 264306)
@@ -802,3 +802,5 @@
# Newly imported test that is flaky on WebKit2 only.
imported/w3c/web-platform-tests/html/browsers/the-window-object/apis-for-creating-and-navigating-browsing-contexts-by-name/no_window_open_when_term_nesting_level_nonzero.window.html [ Pass Failure ]
+# Closed port web socket delays are only supported on wk2.
+http/tests/websocket/tests/hybi/closed-port-delay.html [ Pass ]
Modified: trunk/Source/WebKit/ChangeLog (264305 => 264306)
--- trunk/Source/WebKit/ChangeLog 2020-07-13 17:36:21 UTC (rev 264305)
+++ trunk/Source/WebKit/ChangeLog 2020-07-13 17:57:59 UTC (rev 264306)
@@ -1,3 +1,31 @@
+2020-07-13 Kate Cheney <[email protected]>
+
+ Add artificial delay to WebSocket connections to mitigate port scanning attacks
+ https://bugs.webkit.org/show_bug.cgi?id=213143
+ <rdar://problem/64308927>
+
+ Reviewed by Alex Christensen.
+
+ When establishing web socket connections, closed ports typically
+ return an error more quickly than open ports due to the additional
+ time it takes open ports to perform a TLS handshake. This patch
+ adds a delay to the closed-port case to prevent distinguishing these
+ cases.
+
+ Test: http/tests/websocket/tests/hybi/closed-port-delay.html
+
+ * NetworkProcess/NetworkSocketStream.cpp:
+ (WebKit::NetworkSocketStream::NetworkSocketStream):
+ (WebKit::randomDelay):
+ Add a random delay between 10 and 100ms before sending IPC.
+
+ (WebKit::NetworkSocketStream::sendDelayedFailMessage):
+ (WebKit::NetworkSocketStream::didFailSocketStream):
+ Only delay for the proper error code indicating the connection
+ was refused for a closed port.
+
+ * NetworkProcess/NetworkSocketStream.h:
+
2020-07-13 Zan Dobersek <[email protected]>
[GTK][WPE] WTR and API-tests timing out after r264283
Modified: trunk/Source/WebKit/NetworkProcess/NetworkSocketStream.cpp (264305 => 264306)
--- trunk/Source/WebKit/NetworkProcess/NetworkSocketStream.cpp 2020-07-13 17:36:21 UTC (rev 264305)
+++ trunk/Source/WebKit/NetworkProcess/NetworkSocketStream.cpp 2020-07-13 17:57:59 UTC (rev 264306)
@@ -44,6 +44,7 @@
: m_identifier(identifier)
, m_connection(connection)
, m_impl(SocketStreamHandleImpl::create(url, *this, sessionID, credentialPartition, WTFMove(auditData), NetworkStorageSessionProvider::create(networkProcess, sessionID).ptr()))
+ , m_delayFailTimer(*this, &NetworkSocketStream::sendDelayedFailMessage)
{
}
@@ -101,10 +102,29 @@
send(Messages::WebSocketStream::DidUpdateBufferedAmount(amount));
}
+static const auto delayMaxMilliseconds = 100;
+static const double delayMinMilliseconds = 10;
+static const auto closedPortErrorCode = 61;
+
+static Seconds randomDelay()
+{
+ return Seconds::fromMilliseconds(delayMinMilliseconds + static_cast<double>(cryptographicallyRandomNumber() % delayMaxMilliseconds));
+}
+
+void NetworkSocketStream::sendDelayedFailMessage()
+{
+ send(Messages::WebSocketStream::DidFailSocketStream(m_closedPortError));
+}
+
void NetworkSocketStream::didFailSocketStream(SocketStreamHandle& handle, const SocketStreamError& error)
{
ASSERT_UNUSED(handle, &handle == m_impl.ptr());
- send(Messages::WebSocketStream::DidFailSocketStream(error));
+
+ if (error.errorCode() == closedPortErrorCode) {
+ m_closedPortError = error;
+ m_delayFailTimer.startOneShot(randomDelay());
+ } else
+ send(Messages::WebSocketStream::DidFailSocketStream(error));
}
IPC::Connection* NetworkSocketStream::messageSenderConnection() const
Modified: trunk/Source/WebKit/NetworkProcess/NetworkSocketStream.h (264305 => 264306)
--- trunk/Source/WebKit/NetworkProcess/NetworkSocketStream.h 2020-07-13 17:36:21 UTC (rev 264305)
+++ trunk/Source/WebKit/NetworkProcess/NetworkSocketStream.h 2020-07-13 17:57:59 UTC (rev 264306)
@@ -28,8 +28,10 @@
#include "MessageReceiver.h"
#include "MessageSender.h"
#include "WebSocketIdentifier.h"
+#include <WebCore/SocketStreamError.h>
#include <WebCore/SocketStreamHandleClient.h>
#include <WebCore/SocketStreamHandleImpl.h>
+#include <WebCore/Timer.h>
#include <pal/SessionID.h>
namespace IPC {
@@ -62,6 +64,7 @@
void didFailSocketStream(WebCore::SocketStreamHandle&, const WebCore::SocketStreamError&) final;
private:
+ void sendDelayedFailMessage();
IPC::Connection* messageSenderConnection() const final;
uint64_t messageSenderDestinationID() const final;
@@ -70,6 +73,8 @@
WebSocketIdentifier m_identifier;
IPC::Connection& m_connection;
Ref<WebCore::SocketStreamHandleImpl> m_impl;
+ WebCore::Timer m_delayFailTimer;
+ WebCore::SocketStreamError m_closedPortError;
};
} // namespace WebKit