picoCTF Write-up: WebSockFish
Challenge: WebSockFish Category: Web Exploitation / Client-Side Description: Can you win in a convincing manner against this chess bot? He won't go easy on you!
Summary
The challenge presents a web-based chess game where the user plays against a Stockfish AI. The goal is to "win convincingly". The vulnerability lies in the fact that the chess engine (Stockfish) runs entirely on the client-side (in the user's browser). The client is responsible for calculating the game's evaluation score and then reporting that score to the server via a WebSocket. The server blindly trusts this reported score to determine the winner. By intercepting this communication and sending a crafted message, we can trick the server into believing we have won, without needing to play a single move.
Step 1: Initial Analysis
Upon loading the page, we are greeted with a standard chess interface. The opponent is a "WebSockFish" bot. Playing a few moves reveals that the bot is indeed quite strong, as expected from Stockfish. The hint "win in a convincing manner" suggests that simply checkmating the bot through normal gameplay is either impossible or not the intended solution.
The next step is to examine the client-side source code. The main HTML file reveals several interesting scripts being loaded:
chess.min.js
: A library for chess logic (move generation, validation, etc.).chessboard-1.0.0.min.js
: A library for rendering the visual board.stockfish.min.js
: The actual chess engine, compiled to JavaScript to run in the browser.
The most critical piece of the puzzle is the inline JavaScript at the bottom of the page. It contains the game logic and the communication with the server.
Step 2: Identifying the Vulnerability
Let's analyze the key JavaScript snippet:
// --- WebSocket Communication ---
var ws_address = "ws://" + location.hostname + ":" + location.port + "/ws/";
const ws = new WebSocket(ws_address);
function sendMessage(message) {
ws.send(message);
}
// --- Stockfish Logic ---
var stockfish = new Worker("js/stockfish.min.js");
// ... (code to send moves to stockfish) ...
stockfish.onmessage = function (event) {
var message;
// console.log(event.data);
if (event.data.startsWith("bestmove")) {
// This part makes the bot's move on the board
// ...
} else if (event.data.startsWith(`info depth ${DEPTH}`)) {
// This part is the vulnerability!
var splitString = event.data.split(" ");
if (event.data.includes("mate")) {
message = "mate " + parseInt(splitString[9]);
} else {
// It gets the evaluation score from the local engine...
message = "eval " + parseInt(splitString[9]);
}
// ...and sends it directly to the server!
sendMessage(message);
}
};
This code reveals a classic Client-Side Trust vulnerability:
- After the player (White) makes a move, the browser asks its local Stockfish engine to analyze the position.
- Stockfish analyzes from the perspective of the current player, which is now Black (the bot).
- Stockfish reports its evaluation. A positive score (
eval 100
) means Black is winning. A negative score (eval -100
) means Black is losing. Amate 1
means Black can force a checkmate in 1 move. - The JavaScript code takes this evaluation and sends it directly to the server using
sendMessage(message)
. - The server receives this message (e.g.,
eval 100
) and uses it to determine the state of the game. It implicitly trusts that the client is sending a valid, un-tampered evaluation.
We don't need to win the game. We just need to tell the server that we won.
Step 3: Exploitation
Since the sendMessage
function is defined in the global scope of the page's JavaScript, we can call it directly from the browser's developer console.
Our goal is to send a message that signifies a "convincing win" for us (White). This means sending a message that represents a crushing loss for the bot (Black). In chess engine terms, this is a large negative evaluation score.
- Navigate to the challenge URL:
http://verbal-sleep.picoctf.net:54668/
- Open the Developer Tools in your browser (usually by pressing
F12
orCtrl+Shift+I
). - Switch to the Console tab.
We need to craft a message that says the bot is in an indefensible position. A very large negative evaluation will accomplish this. In the console, type the following command and press Enter:
sendMessage("eval -99999")
eval
: This mimics the message type the client sends.-99999
: This is a massive negative score, indicating that the current player (the bot) is losing by an insurmountable margin.
Immediately after sending the command, the server receives the message, believes we have achieved a completely winning position, and sends the flag back via the WebSocket. The flag appears in the fish's chat bubble on the page.
Alternative Attempt and Analysis
One might first try sending sendMessage("mate 1")
. This results in the server replying with "Haha I think you're gonna drown in 1 moves." This is because a positive mate score (mate 1
) tells the server that the current player (the bot) has a mate in 1. The logical next step would be to send sendMessage("mate -1")
, which signals the opponent (us) has the mate. While this is a valid line of thinking, the eval
method proved to be the one that worked, indicating the server's logic was more robustly tied to the centipawn evaluation.
Conclusion
This challenge was a great example of why critical application logic, especially concerning win/loss conditions, must always be handled and validated on the server-side. The server should have run its own instance of the chess engine to verify the game state and evaluation, rather than trusting the data provided by the client. By manipulating the client-side code, we were able to control the information sent to the server and claim a victory.
Comments
Post a Comment