Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket attack DoSes server, socket.close() does not work #24292

Closed
alexgleason opened this issue Jun 20, 2024 · 1 comment · Fixed by #24301
Closed

WebSocket attack DoSes server, socket.close() does not work #24292

alexgleason opened this issue Jun 20, 2024 · 1 comment · Fixed by #24301
Labels
needs discussion this topic needs further discussion to determine what action to take needs investigation requires further investigation before determining if it is an issue or not web related to Web APIs

Comments

@alexgleason
Copy link
Contributor

Version: Deno 1.42.3

I've discovered a fun thing where you can go to just about any Deno server with a WebSocket endpoint, and run cat /dev/urandom | websocat wss://some-deno.xyz/ws and DoS the server instantly. It doesn't crash, just locks up because the CPU goes to 100%.

So, for the past 5 days I've been pulling my hair out trying to get rate-limiting to work, hitting all kinds of walls with everything under the sun not having good enough support for websockets (Nginx, Hono, hono-rate-limiter)

And now I just discovered that calling socket.close() on the server does not actually sever the connection. 🤣 It just sends a "close" frame and hopes the other side respects it.

Here's my minimal reproduction:

import { Hono } from '@hono/hono';

const app = new Hono();

app.get('/', (c) => {
  const { socket, response } = Deno.upgradeWebSocket(c.req.raw);

  socket.onmessage = (_e) => {
    socket.close();
  };

  return response;
});

Deno.serve(app.fetch);

If you run wscat -c ws://localhost:8000/ and hit Enter, it successfully closes, because wscat respects close frames.

But just fire up websocat and you're in business. cat /dev/urandom | websocat ws://localhost:8000/ absolutely destroys the server, and there is no way to stop it!

This is not really a vulnerability, it's just the way WebSocket works. 🤦 You have to rate-limit it just like HTTP. But rate-limiting sockets seems impossible right now.

@alexgleason
Copy link
Contributor Author

alexgleason commented Jun 20, 2024

The problem is once you're in the onmessage callback there's no way to get out of it. You can throw, but it's in a callback so it kills the whole Deno process. There is simply no API to force close the connection from the server side and keep the process running. God have mercy on my soul.

EDIT: I have a suggestion about how to handle this subtly in the API. Just check the error code passed to close(). So that calling socket.close(1008) will force-close the connection. Then a Deno-specific API isn't needed.

EDIT 2: The socket is permanently in CLOSING state after calling "close()":

import { Hono } from '@hono/hono';

const app = new Hono();

app.get('/', (c) => {
  const { socket, response } = Deno.upgradeWebSocket(c.req.raw);

  socket.onmessage = (_e) => {
    socket.close(1008);
    console.log(socket.readyState);
  };

  return response;
});

Deno.serve(app.fetch);

Output:

2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2

@bartlomieju bartlomieju added web related to Web APIs needs investigation requires further investigation before determining if it is an issue or not needs discussion this topic needs further discussion to determine what action to take labels Jun 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs discussion this topic needs further discussion to determine what action to take needs investigation requires further investigation before determining if it is an issue or not web related to Web APIs
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants