Cheese Chess
Plongée dans l'univers des échecs, version un peu exotique...
Description du challenge
- Nom du CTF : 404CTF 2025
- Catégorie : Web
- Difficulté : Facile
- Date : Mai 2025
On nous donne le challenge suivant :
En ouvrant le site, on apprend par le titre de la page qu'il s'agit d'un site conçu en React.
Sans perdre de temps, je me rends dans les DevTools pour chercher du code pouvant être intéressant.
Le coeur de l'application React est assez basique. Le fichier App.js appelle un composant qui contient la logique de l'ensemble du jeu.
Analyse du code React
Le code du composant est alors le suivant :
import React, { useState } from "react";
import { Chess } from "chess.js";
import { Chessboard } from "react-chessboard";
const ChessGame = () => {
const [game, setGame] = useState(new Chess());
const [gameOver, setGameOver] = useState(false);
const flag = process.env.REACT_APP_FLAG;
const makeMove = (move) => {
const newGame = new Chess(game.fen());
try {
const result = newGame.move(move);
if (result) {
setGame(newGame);
setTimeout(() => makeAIMove(newGame), 500);
checkGameOver(newGame);
return true;
}
} catch (error) {
console.error("Invalid move:", error);
}
return false;
};
const makeAIMove = (newGame) => {
const possibleMoves = newGame.moves({ verbose: true });
if (possibleMoves.length === 0) return;
// Prioritize capturing moves
const capturingMoves = possibleMoves.filter(move => move.captured);
const move = capturingMoves.length > 0
? capturingMoves[Math.floor(Math.random() * capturingMoves.length)]
: possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
newGame.move(move);
setGame(new Chess(newGame.fen()));
checkGameOver(newGame);
};
const checkGameOver = (newGame) => {
const pieceCounts = newGame.board().flat().reduce((acc, piece) => {
if (piece) acc[piece.type] = (acc[piece.type] || 0) + 1;
return acc;
}, {});
if (pieceCounts["k"] === 2 && Object.keys(pieceCounts).length === 1) {
setGameOver(true);
}
};
return (
<div className="chess-container relative">
<div className="flex items-center justify-center h-screen relative z-10">
<Chessboard
position={game.fen()}
onPieceDrop={(source, target) => {
const move = { from: source, to: target, promotion: "q" };
return makeMove(move);
}}
boardWidth={Math.min(window.innerWidth * 0.9, window.innerHeight * 0.9)}
/>
</div>
{gameOver && <div className="absolute inset-0 flex items-center justify-center z-0">
<div className="text-xl font-bold text-green-600">{flag}</div>
</div>}
</div>
);
};
export default ChessGame;
On peut remarquer plusieurs choses :
-
Les règles des échecs sont à priori respectées par l'import du module
chess.js
. Seuls les coups autorisés sont joués par le robot grâce à l'appel de la fonction .moves(): -
Le flag est contenu dans une variable d'environnement :
const flag = process.env.REACT_APP_FLAG;
La solution pour l'obtenir est alors de satisfaire la condition dans la fonction checkGameOver
:
if (pieceCounts["k"] === 2 && Object.keys(pieceCounts).length === 1) {
setGameOver(true);
}
Si l'on parvient à avoir uniquement 2 rois sur le plateau et que le roi est l'unique type de pièce restant, alors cela changera la valeur de la variable d'état à true
.
On pourra alors obtenir le flag un peu plus loin avec l'apparition de son div
conditionné par cette variable d'état :
{gameOver && <div className="absolute inset-0 flex items-center justify-center z-0">
<div className="text-xl font-bold text-green-600">{flag}</div>
</div>}
404CTF{l3_nOuveAU_c4RlseN}