Sécuriser les événements serveur RedM : chaque TriggerServerEvent est une entrée d’attaquant
Comment sécuriser les événements serveur RedM : chaque TriggerServerEvent est une entrée d’attaquant, règles de validation et apport d’un anticheat.
Si vous voulez sécuriser les événements serveur RedM, partez d’un fait : le joueur contrôle le client, donc le joueur contrôle chaque argument de chaque TriggerServerEvent. L’argent et les objets qui disparaissent des serveurs de roleplay ne partent que rarement par un piratage mémoire astucieux. Ils partent par un gestionnaire qui a fait confiance à un nombre qu’on lui a donné.
Ce guide s’adresse aux développeurs de ressources et aux propriétaires de serveur qui relisent des scripts. Les exemples sont en Lua, sans dépendre d’un framework. Les mêmes règles s’appliquent à toute ressource VORP, RSG ou maison, et ce sont elles qui font échouer un exploit de trigger RedM typique au lieu de le laisser payer.
Pourquoi un événement déclenché par le client est une entrée contrôlée par l’attaquant
Un événement serveur est une porte publique. Votre script client appelle TriggerServerEvent("shop:sell", item, count, price), et le serveur exécute le gestionnaire. Rien dans cet appel ne prouve que la requête vient de votre script. Un mod menu ou une ressource injectée peut appeler le même événement, avec le même nom, et mettre ce qu’il veut dans les arguments.
Cacher le nom de l’événement n’y change rien. Le Lua côté client est livré à tous les joueurs, et les noms d’événements s’y trouvent. Traitez le nom comme public et les arguments comme hostiles, comme un développeur web traite un champ de formulaire.
Voilà toute la classe des exploits VORP en une phrase : un gestionnaire qui applique ce que dit le client. Le reste de ce guide explique comment arrêter de faire cela.
Un gestionnaire vulnérable, et sa version corrigée
Voici la forme que nous voyons le plus souvent. Le client envoie l’objet, la quantité et le prix, et le serveur paie.
RegisterNetEvent('shop:sell', function(item, count, price)
local src = source
-- croit le client sur parole pour tout
removeItem(src, item, count)
addMoney(src, count * price)
end)Chaque ligne est un bug. L’appelant choisit le prix, donc un seul appel avec un price énorme fabrique de l’argent. Un count négatif transforme le retrait en cadeau. Un objet que le joueur ne possède pas peut ne jamais être vérifié. Le gestionnaire ne demande pas non plus si le joueur se trouve devant une boutique.
La version corrigée garde les prix sur le serveur, valide les types et les plages, confronte le joueur au monde, et refuse discrètement dès que quelque chose cloche. removeItem, addMoney et getItemCount représentent les propres fonctions de votre framework.
local PRICES = { apple = 2, bread = 3 } -- l'autorité vit ici
local SHOP = vector3(-324.0, 803.0, 117.0) -- coordonnées d'exemple
local lastCall = {}
local function refuse(src, why)
print(('[shop:sell] refused src=%s reason=%s'):format(src, why))
end
RegisterNetEvent('shop:sell', function(item, count)
local src = source -- ne jamais croire un id venu du client
local now = GetGameTimer()
if lastCall[src] and now - lastCall[src] < 1000 then
return refuse(src, 'rate')
end
lastCall[src] = now
if type(item) ~= 'string' or type(count) ~= 'number'
or count % 1 ~= 0 or count < 1 or count > 50 then
return refuse(src, 'bad_args')
end
local price = PRICES[item]
if not price then return refuse(src, 'unknown_item') end
local ped = GetPlayerPed(src)
if #(GetEntityCoords(ped) - SHOP) > 5.0 then
return refuse(src, 'too_far')
end
if getItemCount(src, item) < count then
return refuse(src, 'not_owned')
end
removeItem(src, item, count)
addMoney(src, count * price) -- le serveur calcule la récompense
end)
AddEventHandler('playerDropped', function() lastCall[source] = nil end)Le client n’envoie plus que deux valeurs, et le serveur les traite toutes deux comme de simples suggestions. Chaque règle de ce gestionnaire mérite son propre paragraphe.
Les règles pour sécuriser les événements serveur RedM
1. Identifiez le joueur avec source, et seulement source
Dans un gestionnaire d’événement serveur, source est le joueur qui l’a déclenché, et il est défini par la plateforme, pas par le client. Capturez-le dans une variable locale dès le début (local src = source) car il peut changer au fil d’un yield. N’acceptez jamais un argument « id du joueur » ou « id de la cible » pour décider qui est payé ou qui est blessé.
2. N’acceptez jamais montants, prix, noms d’objets ou cibles comme vrais
Si le client doit dire ce qu’il veut, cherchez la valeur vous-même. Les prix, les récompenses et les listes d’objets appartiennent à une table côté serveur. Si un argument est une cible (un autre joueur, une entité, une coordonnée), vérifiez qu’elle existe et que l’appelant a le droit d’agir dessus. Les événements les plus sûrs n’ont presque aucun argument.
3. Validez les types et les plages
Lua passera sans broncher une table là où vous attendiez une chaîne, ou -1, ou un flottant, ou un nombre si grand que l’arithmétique déraille. Vérifiez type(), exigez des entiers quand vous voulez des entiers, et fixez une borne haute stricte qui correspond à ce que la fonctionnalité peut légitimement faire. Rejetez, ne bornez pas : une requête ramenée à la borne ne vous dit toujours rien sur celui qui l’a envoyée.
4. Vérifiez la distance, le métier et l’état côté serveur
Une vente légitime se fait dans une boutique, un craft légitime à un établi, une arrestation légitime par un représentant de la loi. Vérifiez-le à partir des données du serveur : la position du joueur (avec OneSync, les natives serveur comme GetPlayerPed et GetEntityCoords fonctionnent), son métier depuis votre framework, son état actuel depuis vos propres tables. Une interface client qui cache un bouton n’est pas une vérification.
5. Laissez le serveur calculer la récompense
Le schéma le plus solide est aussi le plus simple : le client dit « j’ai terminé », le serveur décide de ce que cela valait. Pour les activités plus longues, démarrez l’activité sur le serveur et enregistrez quand. Quand le client signale la fin, vérifiez que assez de temps s’est écoulé et qu’une session existe, puis payez un montant défini côté serveur.
6. Limitez le débit de chaque événement qui déplace de la valeur
Un gestionnaire correct une fois peut quand même être appelé deux cents fois par seconde. Gardez un horodatage ou un compteur par joueur, et écartez ce qui dépasse ce qu’un humain pourrait faire. Nettoyez la table sur playerDropped, comme dans l’exemple. Les rejeux et les inondations de requêtes mettent aussi le serveur sous tension, donc un retour anticipé peu coûteux compte.
7. N’exposez pas d’événements d’administration aux clients
Un événement nommé admin:giveMoney qui ne vérifie rien est un cadeau. Si les outils du staff doivent passer par des événements, vérifiez l’appelant sur le serveur à chaque fois, avec le système de permissions du framework ou ACE, pas avec un indicateur côté client. Mieux encore, utilisez des commandes enregistrées sur le serveur, ou des événements qui ne sont jamais déclenchés que de serveur à serveur.
8. Journalisez les refus
La fonction refuse ci-dessus n’est pas de la décoration. Un refus est un signal : soit un bug dans votre propre script client, soit quelqu’un qui sonde. Une ligne par refus, avec le joueur et la raison, transforme vos journaux en liste de pistes. Une précaution : un refus seul ne prouve rien, donc relisez avant de bannir.
Où cela dérape en pratique
Les scripts hérités. Une ressource payante ou ayant fuité qui fait confiance à ses arguments reste vulnérable tant que personne ne la lit. Cherchez
RegisterNetEventetRegisterServerEventdans les ressources de votre serveur et lisez chaque gestionnaire qui touche à l’argent, aux objets, aux métiers ou à l’inventaire.Les événements de debug « temporaires ». L’événement ajouté pour tester et jamais retiré.
Faire confiance à un indicateur défini par le client.
isAdmin,isOnDutyet valeurs similaires envoyées en arguments ne sont que d’autres entrées d’attaquant.Ne corriger qu’un gestionnaire. Si trois ressources paient via la même fonction d’inventaire, l’appelant le plus faible décide de votre sécurité.
Quand le mal est déjà fait, le schéma dans les données ressemble à un dupe ou à une fortune soudainement atypique. Notre article sur l’exploit de dupe d’argent sur RedM explique comment le repérer côté économie.
Ce qu’un anticheat ajoute à la protection des événements RedM
Des gestionnaires soignés suppriment la vulnérabilité. Un anticheat guette ceux qui essaient quand même, et les gestionnaires que vous n’avez pas encore relus. Baobab AC fait cela côté serveur, et son catalogue compte quatre modules qui comptent ici. Voyez toutes les fonctionnalités pour la liste complète.
Anti Blacklist Trigger empêche les menus de triche d’appeler des événements serveur cachés pour faire des choses comme des changements de métier non autorisés ou contourner les règles d’économie.
Anti Spam Trigger bloque les inondations de requêtes visant à submerger le backend du serveur.
Anti Economy Trigger Exploit empêche les tricheurs d’injecter de fausses commandes cachées pour se donner de l’argent, de l’or ou des objets de valeur.
Anti Native Injection accroche les natives qui créent des entités et changent l’état du joueur, de sorte qu’une apparition ou un changement d’état qu’aucune ressource serveur n’a demandé est repéré au moment où il se produit.
Sur les offres Pro et Network, Economy Forensics ajoute un audit en lecture seule de la base de données de votre jeu, recoupé avec le temps de jeu que seul l’anticheat connaît : masse monétaire dans le temps, fortunes atypiques, dupes d’objets, piles impossibles. Chaque résultat est une piste à relire par un humain, jamais une sanction automatique. La détection est agnostique du framework, et VORP Core est la référence pour les modules d’économie. Le guide de l’anticheat VORP détaille cette configuration, et comment un mod menu se fait repérer explique le versant détection de la même histoire.
Vous pouvez voir à quoi ressemble ce résultat dans le tableau de bord de démo, et comparer les offres sur la page des tarifs.
Ce qu’un anticheat ne peut pas faire
Trois autres limites méritent d’être dites. La détection est probabiliste, donc la posture livrée est l’observation : une détection douteuse coûte une ligne dans une liste, pas une expulsion. Economy Forensics vous dit que quelque chose paraît anormal, pas pourquoi, et un humain doit le lire. Et un anticheat voit le client et le trafic, pas votre logique métier, donc vous seul savez qu’une vente à 400 dollars dans cette boutique est impossible.
Les deux couches se complètent. L’autorité côté serveur rend l’exploit impossible. L’anticheat repère la tentative, et l’audit montre ce qui est passé avant que vous corrigiez. Si vous voulez l’argument plus général sur la raison pour laquelle la confiance doit résider sur le serveur, lisez anticheat côté serveur contre côté client.
Une checklist pour sécuriser les événements serveur RedM dès aujourd’hui
- 1
Listez chaque
RegisterNetEventetRegisterServerEventde vos ressources. - 2
Pour chacun qui touche à l’argent, aux objets, aux métiers ou à l’inventaire, lisez le gestionnaire.
- 3
Remplacez chaque prix, récompense et id de joueur fournis par le client par une recherche côté serveur ou par
source. - 4
Ajoutez des vérifications de type et de plage, puis une vérification de position ou d’état.
- 5
Ajoutez une limite de débit par joueur et un journal des refus.
- 6
Retirez les événements de debug et d’administration qui sautent les vérifications.
- 7
Seulement ensuite, activez les modules d’anticheat pour couvrir ce que la relecture a manqué. Démarrez en observation, lisez les alertes, et resserrez à partir de là. Comment fonctionne un anticheat RedM explique la chaîne de traitement.
Un gestionnaire trop confiant est peu coûteux à écrire et coûteux à retrouver. Une heure passée à relire vos événements serveur maintenant coûte moins qu’une nuit d’économie volée.