WebSockFish picoCTF 2025 Solution

Published: April 2, 2025

Description

The chess bot relays evaluation updates over a WebSocket. Instead of playing perfectly, hijack the sendMessage helper in the browser console and feed the server ridiculous evaluation scores.

Open the site and view-source / open DevTools. In the Network tab filter for WS to see the live WebSocket; in the Sources tab grep the script for sendMessage so you know it's a global function attached to window.

Switch to the DevTools Console tab. Because sendMessage is global, calling sendMessage("ping") from the console sends a frame on the same WebSocket the game uses.

  1. Step 1Trigger the mate branch
    Sending sendMessage("mate 1") updates the chat to "drown in 1 moves," proving the server accepts arbitrary client messages.
    bash
    sendMessage("mate 1")
    Learn more

    WebSockets provide full-duplex communication channels over a single TCP connection. Unlike HTTP where the client always initiates requests, WebSocket connections allow both the client and server to send messages at any time after the initial handshake. They are commonly used in real-time applications: chat systems, live feeds, multiplayer games, and collaborative editing tools.

    The browser's DevTools WebSocket inspector (in the Network tab, filter for "WS") shows all messages exchanged on open WebSocket connections. You can see both sent (green) and received (red) frames in real time. When a function like sendMessage is assigned to a JavaScript global, calling it from the browser console is identical to the application calling it internally; the server has no way to distinguish the source.

    This is a fundamental principle of client-side trust issues: any JavaScript variable, function, or WebSocket connection accessible from the browser console can be manipulated by the user. Security-critical logic (access control, game state, score validation) must always be enforced server-side. Relying on client-side checks is equivalent to asking users to validate their own inputs, a guarantee that cannot be made. Web challenges and real-world bug patterns covers more business-logic bypasses.

  2. Step 2Force a wild eval
    The server checks eval < -50000 before handing out the flag, so anything more negative than -50000 wins. Sending -1,000,000,000 is overkill; even -51000 works. Use a value with margin (say -100000) so an off-by-one in your reading of the threshold doesn't cost you the round.
    bash
    sendMessage("eval -100000")
    Learn more

    Chess engines express position evaluations in centipawns, hundredths of a pawn value. An evaluation of 0 is a dead-level position; +100 means white is ahead by roughly one pawn; +300 means white is ahead by a minor piece. Evaluations beyond ±500 typically indicate a decisive advantage, and beyond ±1000 (10 pawns equivalent) suggest a near-certain win. Values like -1,000,000,000 are physically impossible in real chess analysis.

    The server's failure to validate that the evaluation falls within a reasonable range is a classic input validation vulnerability. Even for data that "should" only come from a trusted source (like a chess engine), the server should verify constraints before acting on them. If the evaluation comes from the client over WebSocket, any user can forge it, as this challenge demonstrates.

    This vulnerability pattern appears in many real-world gaming cheats and fraud schemes: forging score submissions to leaderboard APIs, sending impossible game state updates to multiplayer servers, or manipulating auction bids or financial calculations sent client-to-server. Server-side validation is the only reliable defense; client-side validation is UX polish, not security.

  3. Step 3Grab the response
    The flag arrives as an inbound WebSocket frame and the page renders it as a chat message in the game's chat window. If you'd rather not look at the UI, watch the WS frames in DevTools (Network tab, filter WS, click the connection, then the Messages tab) and the picoCTF flag will be visible in the green inbound frame as soon as the server processes your eval.
    Learn more

    The speed of this exploit (a single WebSocket message yields the flag instantly) illustrates how quickly server-side trust of client data can be exploited. There is no need to play a single chess move, bypass authentication, or understand the chess engine. The attack requires only the ability to read JavaScript source (which is always possible in a browser) and call a function from the console.

    The right way to frame this finding is not "business logic bug" but trust boundary violation. The client owns its WebSocket connection completely: it can send any frame at any time, with any payload, regardless of what the page's JavaScript would normally produce. That's a property of how WebSockets work, not a flaw in the client. The server's mistake is treating frames from that channel as if they came from a trusted internal component (the chess engine) when they actually came from an untrusted source (the user's browser, which the user controls). Once you draw the trust boundary at the network edge, the fix becomes obvious: the server must compute its own evaluation from the board state and ignore whatever number the client claims.

    For developers building WebSocket applications: treat every WebSocket message from a client as untrusted input, validate all fields against expected types and ranges, maintain authoritative state server-side (never trust the client's reported state), and use rate limiting to prevent abuse. The same security principles that apply to REST API endpoints apply equally to WebSocket message handlers.

Flag

picoCTF{c1i3nt_s1d3_w3b_s0ck3t5_e5e7...}

Because the logic runs entirely in the client, you don't need chess knowledge; just forge the WebSocket message.

Want more picoCTF 2025 writeups?

Useful tools for Web Exploitation

Related reading

What to try next