Blastlands est multijoueur sur serveur dédié. C'est une position très différente d'un jeu solo : l'état de jeu ne vit pas sur la machine du joueur, donc la triche sur l'état (se donner 99 bombes, ignorer les flammes, traverser les murs) n'est pas « rendue difficile », elle est structurellement impossible. Le client n'envoie que des inputs ; il ne peut pas affirmer une position, un kill, ou un power-up.
Ce que ça déplace, ce n'est pas le problème : ça le change. Les vecteurs qui restent sont l'abus du lobby, l'automatisation côté client, et le déni de service.
| Vecteur | Pourquoi ça ne marche pas |
|---|---|
| Éditer sa position / sa vie / son inventaire | Le serveur ne lit jamais l'état envoyé par le client. La prédiction locale est cosmétique et se fait corriger au tick suivant. |
| Se déclarer vainqueur, forger un kill | Seul le serveur décide qu'un joueur meurt. Il n'existe pas de message client « j'ai tué X ». |
| Poser plus de bombes que sa capacité | La capacité est de l'état serveur. Un input « poser une bombe » invalide est ignoré, pas appliqué. |
| Rejouer / accélérer le temps | Le temps est un compteur de ticks côté serveur. Un client qui tourne trop vite envoie juste des inputs qui seront traités au rythme du serveur. |
| Save scumming, rollback | Il n'y a pas de save locale qui compte. |
| Se connecter sans passer par le lobby | Le serveur de jeu n'admet une connexion qu'avec un ticket signé par le lobby (HMAC-SHA256, avec le secret propre à ce serveur, reçu à son inscription), lié à la partie et au joueur, avec une expiration, et utilisable une seule fois. Un serveur ne peut admettre personne dans la partie d'un autre. Voir docs/ARCHITECTURE.md. |
| Flood d'inputs | Chaque siège a un budget de paquets (InputRateGate) : une rafale d'une seconde passe, un débit soutenu au-delà de deux paquets par tick est refusé, et un siège qui dépasse pendant une seconde entière est déconnecté. Les inputs pour un tick passé ou trop lointain sont refusés par InputBuffer. |
Ce tableau protège un joueur contre les autres joueurs, pas contre le serveur. Les serveurs
de jeu sont hébergés par les joueurs (voir docs/ARCHITECTURE.md) :
celui qui fait tourner un serveur est l'autorité des parties qui s'y jouent, et peut
tricher sur celles-là. Le lobby borne ce qu'il peut faire ailleurs : il s'annonce à son
adresse réelle (jamais celle qu'il déclare), sa clé et son secret ne valent que pour ses
propres parties, et une adresse ne peut inscrire que quelques serveurs.
Host on this PC ne change rien à ce modèle : le client qui héberge fait tourner un
serveur ordinaire, inscrit avec "listed": false, et reste l'autorité de sa partie comme
n'importe quel serveur de joueur. Un serveur non listé ne reçoit une partie que si la
requête de création présente sa clé (Authorization: Bearer), et cette clé ne quitte
jamais le processus qui l'a reçue. Personne d'autre ne peut donc poser une partie sur le
PC d'un joueur. Le client de l'hôte se connecte en 127.0.0.1 avec un ticket comme les
autres, il ne passe pas par une porte dérobée.
Ces points sont réels et suivis comme des issues, pas comme des acquis.
-
Automatisation côté client (bots humains). Un joueur peut scripter des inputs parfaits. C'est le seul cheat qui survit à un serveur autoritaire, dans tous les jeux. Mitigation réaliste : détection statistique côté serveur (temps de réaction, régularité inhumaine des timings), pas d'anti-cheat kernel.
-
Abus du lobby. Création massive de parties, squat de slots, noms abusifs. Traité (#22, #23) : rate limit par adresse sur
POST /v1/matcheset sur les joins, plafond de parties hébergées simultanément par une adresse, TTL sur les parties que personne n'a rejointes, et moissonnage des parties dont l'instance ne donne plus signe de vie. Les pseudos étaient déjà contraints en longueur et en charset côté serveur.Une quatrième forme d'abus manquait à cette liste et a été corrigée depuis :
POST /v1/matches/{id}/startne vérifiait rien. Les identifiants de parties sont publics,GET /v1/matchesles donne à qui les demande, donc n'importe qui pouvait parcourir la liste et démarrer chaque partie ouverte. Une partie démarrée sort du listing et refuse les joins, ce qui suffisait à rendre le lobby inutilisable, une requête non authentifiée par partie. Le lobby retient maintenant le ticket qu'il a remis à l'hôte et exige qu'il soit présenté pour démarrer.Les sièges sont aussi bornés : un pseudo déjà assis dans une partie ne peut pas reprendre un second siège (
name_taken), et une adresse ne tient qu'un nombre limité de sièges par partie, hôte compris (BLASTLANDS_LOBBY_SEATS_PER_ADDRESS, 2 par défaut, 0 désactive). Sans cela, un script remplissait une partie de faux joueurs sous le rate limit des joins.Une réserve à connaître avant d'exposer le service : l'adresse retenue est celle du socket, sauf si
BLASTLANDS_LOBBY_TRUST_FORWARDED_FORest activé. Derrière un reverse proxy sans ce réglage, tout le trafic partage un seul compteur ; avec ce réglage sur un lobby joignable en direct, n'importe qui peut écrire l'adresse de son choix et les limites ne valent plus rien. Le bon réglage dépend du déploiement, il n'y a pas de valeur juste par défaut.Quand ce réglage est actif, c'est la dernière entrée de
X-Forwarded-Forqui est lue, pas la première. La plupart des proxys ajoutent au lieu de remplacer (le$proxy_add_x_forwarded_forde nginx, les ALB), donc un client qui envoie lui-même l'en-tête arrive sous la forme<ce qu'il a écrit>, <ce que le proxy a vu>: lire le début de cette liste revient à croire l'attaquant, qui se fabriquerait une adresse neuve à chaque requête. Cela suppose exactement un saut de confiance. Avec plusieurs proxys, ou avec un proxy qui laisse passer tel quel un en-tête fourni par le client, même la dernière entrée n'est pas fiable sans retirer un nombre connu de sauts. -
DoS sur le lobby ou sur une instance. Un seul VPS = une seule cible. Mitigation : limites de connexion, et le fait qu'une instance qui tombe ne fait tomber qu'une partie.
- Anti-cheat au niveau noyau. Pas prévu, et pas souhaitable pour un jeu entre potes.
- Bans persistants. Il n'y a pas de comptes. Un ban ne peut porter que sur une IP, ce qui est faible et frappe à côté. Assumé tant que le jeu reste petit.
- Chiffrement du trafic de jeu. Le trafic UDP n'est pas chiffré. Il ne transporte que des positions de bombes ; il n'y a rien de sensible dedans. Le lobby, lui, est en TLS.
Le jeu ne demande pas de compte, pas d'e-mail, pas d'identifiant tiers. Ce qui transite : un pseudo choisi à la volée, et l'IP nécessaire pour établir la connexion. Rien n'est persisté après la fin d'une partie. Toute évolution qui ajouterait de la persistance (stats, classements) est un changement de posture qui doit être documenté ici en même temps.
Ouvrir une issue si c'est bénin. Pour quoi que ce soit d'exploitable à distance (RCE sur
le lobby ou sur une instance, forge de ticket, crash serveur déclenchable par un paquet),
écrire à contact@ferrlabs.com avec SECURITY dans l'objet,
plutôt que d'ouvrir une issue publique.