Ajoute AUDIT.md : revue archi / securite / backend .NET / DCS-Lua / DevOps-DBA du fork de reprise. Constats majeurs P0 : injection SQL via donnees joueur (DatabaseController.cs) et listener TCP non authentifie expose (TCPController.cs). Feuille de route priorisee P0->P3 + plan de reprise inclus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
Audit de reprise — Perun for DCS World
Revue menée pour décider d'une reprise du projet (upstream
szporwolik/perun, archivé le 2026-03-29, licence MIT). Fork de travail :DaKerboul/perun, miroir Gitea :git.kerboul.me/kerboul/perun.Méthode. Revue à 5 casquettes (Architecte, Sécurité, Backend .NET, DCS/Lua, DevOps/DBA). Périmètre audité : le code réellement écrit par le projet — hook Lua (
01_DCS), app C# WinForms (02_Windows_App), wrapper C++ (03_*, hors arbre Lua 5.1.5 vendoré), schéma MySQL (04_MySQL), exemple PHP (05_Misc). Les ~35.c/.hdelua-5.1.5/sont du Lua amont vendoré et ne sont pas audités. Date : 2026-06-11.
Synthèse — verdict
Projet fonctionnellement riche et bien pensé sur le fond (modèle de données propre, couverture événementielle DCS complète, multi-instances, intégrations SRS/LotATC). Mais dette de sécurité critique et code applicatif daté (monolithes, threading artisanal, zéro test, zéro CI, Windows/.NET Framework uniquement). C'est une bonne base à reprendre, à condition de traiter les points P0 avant toute remise en production.
| # | Domaine | Gravité | Sévérité |
|---|---|---|---|
| S1 | Injection SQL via données joueur (app C#) | Critique | 🔴 P0 |
| S2 | Port TCP sans authentification, bind 0.0.0.0 |
Critique | 🔴 P0 |
| S3 | XSS stocké dans l'exemple PHP | Élevé | 🟠 P1 |
| S4 | Mot de passe MySQL stocké en clair | Élevé | 🟠 P1 |
| S5 | PII (IP, UCID) sans rétention ni consentement (RGPD) | Élevé | 🟠 P1 |
| B1 | Threading non synchronisé (buffer partagé) | Élevé | 🟠 P1 |
| B2 | ExecuteReader pour des INSERT/UPDATE, 1 connexion/frame |
Moyen | 🟡 P2 |
| A1 | Monolithes, dynamic, aucun test, aucune CI |
Moyen | 🟡 P2 |
| L1 | Fuite de variables globales dans le hook DCS | Moyen | 🟡 P2 |
| D1 | Lua 5.1.5 vendoré dans le repo, pas de build reproductible | Moyen | 🟡 P2 |
| D2 | Pas de FK, dépendance à STRICT_TRANS_TABLES off, pas de migrations |
Faible | 🟢 P3 |
1. Architecte / Lead — structure & dette
Points forts
- Découpage fonctionnel clair en 5 dossiers numérotés, lisible d'emblée.
- Séparation nette des responsabilités : collecte (Lua) → transport (TCP/DLL) → persistance (C#) → restitution (PHP). Le protocole de trames (IDs 1/2/3/50…101) est documenté dans le README.
- Multi-instances supporté de bout en bout (champ
instancepartout).
Points faibles
- Monolithes.
DatabaseController.SendToMySqlfait ~290 lignes avec unswitchgéant mêlant construction SQL, mapping métier et logging (01_Classes/DatabaseController.cs:12-304). IdemTCPController.StartListen(boucles imbriquées sur ~170 lignes). dynamicpartout pour le JSON entrant (DatabaseController.cs:25,TCPController.cs:121) : aucune validation de schéma, accèsTCPFrame.payload.xqui lèvent au moindre champ manquant → exceptions au lieu d'un rejet propre.- Plateforme verrouillée : VS2017, .NET Framework 4.8, WinForms → Windows uniquement, fin de vie. Or le serveur DCS est Windows, mais l'app de persistance n'a aucune raison d'y être clouée (elle ne parle que TCP + MySQL).
- TODO laissé en dur (commentaire polonais) :
DatabaseController.cs:92« TUTAJ DODAC CATCH TBD » → gestion d'erreur inachevée. - Aucun test, aucune CI, dérive de version (cf. §4 L-version).
À faire
- Extraire la construction SQL dans une couche dédiée (un handler par type de
trame) + DTO typés à la place de
dynamic. - Cibler .NET 8 + un worker headless multiplateforme ; garder l'UI WinForms en option (ou la remplacer par un petit panneau web/CLI).
- Introduire des tests unitaires (parsing de trames, génération SQL) et un pipeline CI.
2. Sécurité (AppSec) — bloquant pour la prod
S1 — Injection SQL via données contrôlées par le joueur. 🔴
L'app paramètre certaines valeurs (@PAR_*) mais en concatène des dizaines
d'autres directement dans le SQL, dont des champs que n'importe quel joueur
maîtrise (UCID, nom, hash de mission, IP, datetime, tous les compteurs ps_*) :
DatabaseController.cs:128-132(chat) :ucid,missionhash,all,datetime.DatabaseController.cs:159-165(stats) :stat_ucid,stat_missionhashet ~30 valeursstat_data_perun.ps_*injectées telles quelles.DatabaseController.cs:175-177(login) :login_ucid,login_ipaddr,login_datetime.DatabaseController.cs:50-51, 77-89, 205-206:instance/typeconcaténés.
Un UCID/nom forgé (ou un module client modifié) permet l'exfiltration ou la
destruction de la base. C'est le défaut n°1 à corriger.
→ Tout passer en requêtes paramétrées (MySqlParameter), sans exception.
S2 — Listener TCP non authentifié, exposé. 🔴
new TcpListener(IPAddress.Any, intListenPort) (TCPController.cs:51) écoute sur
toutes les interfaces, sans authentification ni allowlist. Couplé à S1,
n'importe quel hôte joignant le port (48621 par défaut) injecte des trames
arbitraires → compromission complète de la base.
→ Par défaut bind 127.0.0.1 (hook et app sont quasi toujours sur la même
machine), + secret partagé/HMAC sur les trames, + allowlist d'IP.
S3 — XSS stocké (PHP). 🟠
05_Misc/05_PHP_Example/index.php réinjecte en HTML des données joueur sans
échappement : message de chat (:110), nom (:92, :109, :143),
contenu d'événement (:127). Un joueur dont le nom vaut <script>…</script>
exécute du JS dans le navigateur de l'admin. Les requêtes elles-mêmes sont
statiques (pas de SQLi côté PHP), le risque est l'XSS.
→ htmlspecialchars() systématique sur toute sortie ; corriger aussi le HTML
invalide (<h1> fermé par </h2> ligne 44).
S4 — Identifiants MySQL en clair. 🟠
Le mot de passe est stocké en String dans les user settings .NET
(02_Forms/form_Main.cs:119, clé MYSQL_Password de app.config) → écrit en
clair dans user.config.
→ Chiffrer via DPAPI (ProtectedData) ou déléguer à un gestionnaire de
secrets ; a minima ne jamais journaliser la chaîne de connexion.
S5 — Données personnelles (RGPD). 🟠
Le hook collecte et stocke adresses IP et UCID des joueurs
(Perun-hook.lua:364-366 → pe_DataPlayers_lastip, pe_LogLogins_ip), sans
politique de rétention ni information des joueurs.
→ Définir une durée de rétention + purge, anonymiser/hacher l'IP si non
nécessaire, documenter (mention serveur + Discord).
3. Backend / .NET — qualité & robustesse
B1 — Threading artisanal non synchronisé. 🟠
Le thread TCP écrit dans Globals.arrMySQLSendBuffer (tableau fixe) pendant que
le thread d'envoi le lit, sans verrou (TCPController.cs:130-142). Buffer
plein = paquets silencieusement perdus (:139-141), scan linéaire O(n) par
paquet. Accès concurrents → corruption/race.
→ Remplacer par une BlockingCollection<T>/Channel<T> thread-safe et bornée.
B2 — Accès base inefficace. 🟡
ExecuteReader()utilisé pour exécuter des lots d'INSERT/UPDATE (DatabaseController.cs:217) : devrait êtreExecuteNonQuery().- Une nouvelle
MySqlConnectionouverte/fermée par trame (:34, :292) : pas de réutilisation du pool, surcoût réseau par paquet. - Tout est synchrone bloquant (pas d'
async/await). → Connexion/pool réutilisé, requêtes asynchrones,ExecuteNonQueryAsync.
Autres
- Dépendance
Newtonsoft.Json→ migrable versSystem.Text.Json. - Gestion d'erreurs par numéros MySQL en dur (
:246-279) : utile mais fragile, à compléter (le catch manquant signalé en:92).
4. DCS / Lua / Intégration
Points forts
- Couverture événementielle DCS très complète : kill (avec catégorisation
PvP/AI), friendly fire, crash, eject, takeoff/landing (airfield/ship/FARP),
multicrew, change_slot, connect/disconnect, chat, MOTD. C'est le vrai actif du
projet (
Perun-hook.lua:725-879). - Comptage de stats maison car les stats natives DCS sont peu fiables (choix assumé et pertinent).
Points faibles
- L1 — Fuite de variables globales 🟡 : plusieurs variables sont assignées
sans
localdans l'environnement hook (privilégié et partagé) — ex._temp_killers/_temp_event_type(:754-756),_master_type/_master_slot/_sub_slot(:813),_temp_airfield(:849). Risque de collision avec d'autres hooks installés sur le serveur. - L-version — dérive de version : la version du hook est codée en dur
"v0.12.1"(:30) et vit séparément de la version de l'app (Globals.VersionPerun). → source unique de vérité (tag git → injecté au build). - Coût par frame :
onSimulationFramefait dutable.concat/JSON à chaque frame ; sur serveur chargé, surveiller le budget temps (déjà mesuré en µs dans les logs — bon réflexe à conserver/exposer). - Dépendance à une DLL compilée (
perun.dll, issue de03_Perun_Lua_Wrapper) livrée hors repo : reproductibilité du build à fiabiliser (cf. D1). - TCP en clair, pas de TLS (acceptable en loopback, à revoir si distant).
À faire
localsur toutes les temporaires ; passeluacheck.- Versionner hook + app depuis le tag git.
- Documenter/reproduire le build de
perun.dll(CMake déjà présent).
5. DevOps / DBA / Release
Base de données — plutôt saine.
- InnoDB +
utf8mb4/unicode_ci, PK/AUTO_INCREMENT, clés UNIQUE pertinentes (UCID, hash, type, stats par mission+ucid+type) et index sur les colonnes de tri (datetime,instance,type) —04_MySQL/m1081_perun.sql. - D2 🟢 : pas de
FOREIGN KEY(intégrité référentielle non garantie), dépendance documentée àSTRICT_TRANS_TABLESdésactivé (README:42,84) — c'est-à-dire qu'on s'appuie sur la coercition/troncature silencieuse de MySQL, ce qui masque des bugs. Nom de fichier cryptique (m1081_perun.sql). - Pas d'outil de migration (un seul dump). → introduire des migrations versionnées (Flyway/dbmate/sqitch) et, à terme, des FK + un mode strict assumé.
Build / release / repo
- D1 🟡 : arbre Lua 5.1.5 complet vendoré dans
03_Perun_Lua_Wrapper/lua-5.1.5/→ gonfle le repo et l'audit. Le passer en sous-module / téléchargement au build. - Build manuel VS2017, pas de CI, pas d'artefacts reproductibles.
- Contributions historiquement attendues sur la branche
dev(README:135). - Pas de
.gitignoreracine (un seul dans le wrapper).
À faire
- CI (build C# +
luacheck+ lint PHP + validation du schéma SQL). - Migrations DB versionnées ; activer les FK progressivement.
- Sortir Lua amont du repo ; pipeline de build de la DLL.
- Releases taguées avec binaires (
perun.dll+ app) attachés.
Feuille de route priorisée
P0 — Sécurité bloquante (avant toute prod)
- Paramétrer 100 % des requêtes SQL (S1).
- Bind loopback par défaut + auth/allowlist sur le listener TCP (S2).
P1 — Durcissement & conformité 3. Échappement HTML de l'exemple PHP (S3). 4. Chiffrer le mot de passe MySQL — DPAPI (S4). 5. Rétention/anonymisation IP & UCID, doc RGPD (S5). 6. File thread-safe bornée à la place du buffer tableau (B1).
P2 — Modernisation
7. Découper les monolithes, DTO typés au lieu de dynamic (A1).
8. Connexion poolée + accès DB async + ExecuteNonQuery (B2).
9. local + luacheck sur le hook, version unifiée depuis git (L1).
10. Cibler .NET 8 / worker multiplateforme.
11. Tests + CI.
P3 — Hygiène long terme 12. Migrations DB, FK, mode SQL strict assumé (D2). 13. Désvendoriser Lua, build reproductible de la DLL (D1). 14. Releases taguées + artefacts.
Plan de reprise suggéré
- Geler le comportement : quelques tests de caractérisation sur le parsing de trames et la génération SQL, pour refactorer sans régresser.
- Sprint sécu (P0) sur une branche
security/sql-and-tcp, puis tagv0.13.0-rctesté en loopback. - P1 en incréments livrables.
- Décider de la cible app (garder WinForms vs worker .NET 8) avant d'attaquer P2 — ça oriente tout le refactor.
Adapté à ton contexte : tu fais déjà tourner Gitea + CI sur le cluster ; un pipeline build/lint Perun s'y intègre directement, et la communauté Commus DCS est un terrain de test naturel pour les stats.