🚩
Practical CTF
BlogContact
  • 🚩Home - Practical CTF
  • 🌐Web
    • Enumeration
      • Finding Hosts & Domains
      • Masscan
      • Nmap
      • OSINT
    • Client-Side
      • Cross-Site Scripting (XSS)
        • HTML Injection
        • Content-Security-Policy (CSP)
      • CSS Injection
      • Cross-Site Request Forgery (CSRF)
      • XS-Leaks
      • Window Popup Tricks
      • Header / CRLF Injection
      • WebSockets
      • Caching
    • Server-Side
      • SQL Injection
      • NoSQL Injection
      • GraphQL
      • XML External Entities (XXE)
      • HTTP Request Smuggling
      • Local File Disclosure
      • Arbitrary File Write
      • Reverse Proxies
    • Frameworks
      • Flask
      • Ruby on Rails
      • NodeJS
      • Bun
      • WordPress
      • Angular
    • Chrome Remote DevTools
    • ImageMagick
  • 🔣Cryptography
    • Encodings
    • Ciphers
    • Custom Ciphers
      • Z3 Solver
    • XOR
    • Asymmetric Encryption
      • RSA
      • Diffie-Hellman
      • PGP / GPG
    • AES
    • Hashing
      • Cracking Hashes
      • Cracking Signatures
    • Pseudo-Random Number Generators (PRNG)
    • Timing Attacks
    • Blockchain
      • Smart Contracts
      • Bitcoin addresses
  • 🔎Forensics
    • Wireshark
    • File Formats
    • Archives
    • Memory Dumps (Volatility)
    • VBA Macros
    • Grep
    • Git
    • File Recovery
  • ⚙️Reverse Engineering
    • Ghidra
    • Angr Solver
    • Reversing C# - .NET / Unity
    • PowerShell
  • 📟Binary Exploitation
    • ir0nstone's Binary Exploitation Notes
    • Reverse Engineering for Pwn
    • PwnTools
    • ret2win
    • ret2libc
    • Shellcode
    • Stack Canaries
    • Return-Oriented Programming (ROP)
      • SigReturn-Oriented Programming (SROP)
      • ret2dlresolve
    • Sandboxes (chroot, seccomp & namespaces)
    • Race Conditions
  • 📲Mobile
    • Setup
    • Reversing APKs
    • Patching APKs
    • HTTP(S) Proxy for Android
    • Android Backup
    • Compiling C for Android
    • iOS
  • 🌎Languages
    • PHP
    • Python
    • JavaScript
      • Prototype Pollution
      • postMessage Exploitation
    • Java
    • C#
    • Assembly
    • Markdown
    • LaTeX
    • JSON
    • YAML
    • CodeQL
    • NASL (Nessus Plugins)
    • Regular Expressions (RegEx)
  • 🤖Networking
    • Modbus - TCP/502
    • Redis/Valkey - TCP/6379
  • 🐧Linux
    • Shells
    • Bash
    • Linux Privilege Escalation
      • Enumeration
      • Networking
      • Command Triggers
      • Command Exploitation
      • Outdated Versions
      • Network File Sharing (NFS)
      • Docker
      • Filesystem Permissions
    • Analyzing Processes
  • 🪟Windows
    • The Hacker Recipes - AD
    • Scanning/Spraying
    • Exploitation
    • Local Enumeration
    • Local Privilege Escalation
    • Windows Authentication
      • Kerberos
      • NTLM
    • Lateral Movement
    • Active Directory Privilege Escalation
    • Persistence
    • Antivirus Evasion
    • Metasploit
    • Alternate Data Streams (ADS)
  • ☁️Cloud
    • Kubernetes
    • Microsoft Azure
  • ❔Other
    • Business Logic Errors
    • Password Managers
    • ANSI Escape Codes
    • WSL Tips
Powered by GitBook
On this page
  • # Related Pages
  • Description
  • Protocol
  • SocketIO
  • Snippets
  • Cross-Site WebSocket Hijacking
  • Protections
  1. Web
  2. Client-Side

WebSockets

PreviousHeader / CRLF InjectionNextCaching

Last updated 1 day ago

# Related Pages

Bypassing reverse proxies using

Description

allow two-way communication over a single connection where both the server and client can send messages whenever they like. It is functionally similar to a raw TCP connection sending data to and between, but is wrapped in WebSocket frames and used by the browser.

Protocol

Creating a WebSocket connection starts with an HTTP request. In the browser, you call the constructor and a request like the following is sent:

Request
GET /some/ws HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: ut/YSNzdtIkvnTCSQtTx9g==

The server has a websocket handler for /some/ws, so it responds with a Sec-WebSocket-Accept: header derived from the request's Sec-WebSocket-Key: (). The status code will be 101 "Switching Protocols", and the TCP connection stays open.

Response
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: /fCJAu1M5mY53eHwube2Xl1leKM=

After this handshake, any party can send websocket frames that the other will decode and handle accordingly. On the wire this is a binary protocol, and looks something like this:

Messages have a few different types:

  • Text data frame: Simple UTF-8 strings as message content

  • Binary data frame: Raw bytes as message content

  • Ping/Pong: Used to keep the connection alive and avoid timeouts

  • Close: The party sending a close frame cannot send more frames after doing so. The other may still send frames, but most often it will automatically send a closing handshake response to end the connection from both sides.

Implementations with WebSockets often work completely differently than regular HTTP endpoints, which may cause them to have less validation or more dangerous behaviour. Be sure to test for the standard type of vulnerabilities within fields of a WebSocket message.

SocketIO

Only the server can put you into a room, you cannot decide this for yourself. This is often used for authorization, after completing some verification. This puts you into a private room with other connected clients where sensitive information may be shared.

Snippets

WebSocket Server

Dependencies
npm install ws
server.js
const WebSocket = require('ws');

const ws = new WebSocket.Server({ port: 8080 });

ws.on('connection', conn => {
  console.log('Client connected.');

  conn.on('message', message => {
    console.log(`Received from client: ${message}`);
    conn.send(`Server received: ${message}`);
  });

  conn.on('close', () => {
    console.log('Client disconnected.');
  });

  conn.send('Welcome to the WebSocket server!');
});

console.log('WebSocket server is running on ws://localhost:1337');

WebSocket Client - JavaScript

client.js
const socket = new WebSocket("ws://localhost:1337");

socket.addEventListener("open", (event) => {
  socket.send("Hello Server!");
});

socket.addEventListener("message", (event) => {
  console.log("Message from server ", event.data);
});
WebSocketClient.js
class WebSocketClient {
  constructor(url) {
    this.socket = new WebSocket(url);
    this._messageQueue = [];
    this._pendingResolvers = [];

    this.socket.addEventListener('message', (event) => {
      const message = event.data;
      if (this._pendingResolvers.length > 0) {
        this._pendingResolvers.shift()(message);
      } else {
        this._messageQueue.push(message);
      }
    });
  }

  send(message) {
    if (this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(message);
    } else {
      throw new Error("WebSocket is not open");
    }
  }

  recv() {
    return new Promise((resolve) => {
      if (this._messageQueue.length > 0) {
        resolve(this._messageQueue.shift());
      } else {
        this._pendingResolvers.push(resolve);
      }
    });
  }

  close() {
    if (this.socket.readyState === WebSocket.OPEN) {
      this.socket.close();
    } else {
      throw new Error("WebSocket is already closed or not opened");
    }
  }
}
ws = new WebSocketClient('ws://localhost:8080')
console.log("Received:", await ws.recv())
ws.send("Hello, from JavaScript!")
console.log("Received:", await ws.recv())
ws.close()

WebSocket Client - Python

Dependencies
pip install websocket-client
WebSocketClient.py
import websocket
import threading
import queue


class WebSocketClient:
    def __init__(self, url):
        self.url = url
        self.ws = websocket.WebSocketApp(
            url,
            on_open=self._on_open,
            on_message=self._on_message,
            on_close=self._on_close,
            on_error=self._on_error
        )
        self.recv_queue = queue.Queue()
        self.connected_event = threading.Event()
        self.thread = threading.Thread(target=self.ws.run_forever)
        self.thread.daemon = True

    def _on_open(self, ws):
        self.connected_event.set()

    def _on_message(self, ws, message):
        self.recv_queue.put(message)

    def _on_close(self, ws, code, msg):
        print(f"WebSocket closed: {code} - {msg}")

    def _on_error(self, ws, error):
        print(f"WebSocket error: {error}")

    def send(self, message):
        if isinstance(message, bytes):
            self.ws.send(message, websocket.ABNF.OPCODE_BINARY)
        else:
            self.ws.send(message)

    def recv(self, timeout=5):
        try:
            return self.recv_queue.get(timeout=timeout)
        except queue.Empty:
            raise TimeoutError("No message received in time.")

    def close(self):
        self.ws.close()
        self.thread.join(timeout=1)

    def __enter__(self):
        self.thread.start()
        if not self.connected_event.wait(timeout=5):
            raise TimeoutError("Could not connect to WebSocket server.")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
with WebSocketClient("ws://localhost:1337") as ws:
    print(f"Received: {ws.recv()!r}")
    ws.send("Hello from Python!")
    print(f"Received: {ws.recv()!r}")

SocketIO Server

Dependencies
npm install socket.io express
server-socketio.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
  console.log('Client connected');
  
  // Emitting events
  socket.emit('event1', 'This is sent to the connecting socket only');
  io.emit('event2', 'This is sent to all connected sockets');
  socket.broadcast.emit('event3', 'This is sent to all sockets except the sender');
  
  // Rooms
  socket.join('room1');
  io.to('room1').emit('roomEvent', 'Message to room1');
  
  // Listening for events
  socket.on('clientEvent', (data) => {
    console.log('Received from client:', data);
  });

  socket.on('disconnect', () => {
    console.log('Client disconnected');
  });
});

const PORT = 1337;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

SocketIO Client - JavaScript

Importing (Browser)
<script src="https://cdn.socket.io/4.8.1/socket.io.js"></script>
<script>
  ...
</script>
<!-- or -->
<script type="module">
  import { io } from "https://cdn.socket.io/4.8.1/socket.io.esm.min.js";
  ...
</script>
Dependencies (NodeJS)
npm install socket.io-client
Importing (NodeJS)
import { io } from "socket.io-client";
// or
const { io } = require("socket.io-client");
const socket = io("http://localhost:1337");

function recv(socket, event) {
    return new Promise((resolve) => {
        function handler(data) {
            socket.off(event, handler);
            resolve(data);
        }
        socket.on(event, handler);
    });
}

socket.on('connect', async () => {
    console.log('Connected to server');

    socket.on('someEvent', (data) => {
        console.log('Received from server:', data);
    });

    const listener = recv(socket, 'response');
    socket.emit('clientEvent', 'Hello from client');
    const response = await listener;
    console.log('Response received:', response);

    socket.close();
});
socket.on('disconnect', () => {
    console.log('Disconnected from server');
});

SocketIO Client - Python

Dependencies
pip install "python-socketio[client]"
import socketio

with socketio.SimpleClient() as socket:
    socket.connect('http://localhost:1337')
    
    socket.emit('my message', {'foo': 'bar'})
    event = socket.receive()
    print(f'received event: "{event[0]}" with arguments {event[1:]}')
    
    @socket.event
    def message(data):
        print('I received a message!')
    
    @socket.on('my message')
    def on_message(data):
        print('I received a message!')

Cross-Site WebSocket Hijacking

Protections

Some common protections include:

  • Checking the Origin: header matches a trusted value. Make sure this is no vulnerable prefix/suffix matching, or that dots in a regex match any character.

  • Requiring the authentication token to be sent as a websocket message, not automatically in a cookie during the handshake. The attacker cannot then abuse any authentication because it needs to happen manually.

A common wrapper around WebSockets in the wild is . This has backwards compatability support by falling back on streaming HTTP responses if WebSockets fail for any reason, and has built some more features like session/room management that are common for web applications.

At the highest level, there are that can be seen as completely different connections to different applications. Almost always, this is implicitly the main namespace (/). A namespace contains which can be seen as types of .

WebSocket connections can also be made cross-site, and if these are automatically authenticated by cookies, you can get into a dangerous scenario where an attacker's site can not only send, but also receive messages. This is because doesn't apply to WebSockets, you are always able to read incoming messages cross-origin.

Important for this to have any security impact is if the rules allow any site to send authentication cookies with requests. Because WebSockets are background requests, the SameSite= attribute needs to be None for Chromium, or unset for Firefox. Note that if you can gain control over a same-site origin like a subdomain or different port with XSS, you can even get SameSite=Strict cookies to be sent.

🌐
SocketIO
namespaces
rooms
events
CORS
WebSockets
WebSocket()
source
LogoWebSocket - Web APIs | MDN
Documentation for JavaScript WebSocket Client API
LogoServer API | Socket.IO
Documentation for Socket.IO library's API methods
LogoClient API | Socket.IO
Documentation for Socket.IO-Client library's API methods
LogoThe Socket.IO Clients — python-socketio documentation
Documentation for python-socketio client library
LogoCross-site WebSocket hijacking | Web Security AcademyWebSecAcademy
Explanation of the CSWSH technique with labs
WebSocket & h2c Smuggling
Same-site
Wireshark capture of WebSocket frame with "Hello, world!" text payload