games / fivem
Sécurité : validation serveur et anti-triche
Explication
Ce que vous allez apprendre
- Identifier pourquoi un client FiveM ne peut jamais être considéré comme fiable
- Recalculer côté serveur toute donnée ayant une conséquence économique ou de gameplay
- Mettre en place un rate limiting basique contre le spam d'events
- Valider strictement les types et les bornes de toute donnée reçue d'un client
- Détecter un déplacement physiquement impossible (speedhack, téléportation)
Dans quel contexte ?
Un serveur RP fraîchement ouvert au public constate en quelques heures que plusieurs joueurs disposent de sommes d'argent totalement disproportionnées par rapport à leur temps de jeu. L'enquête révèle qu'un event serveur faisait confiance au prix envoyé directement par le client pour un achat — une faille exploitée en quelques minutes par des joueurs ayant modifié leur client. Cette leçon détaille précisément comment ce genre d'incident, très fréquent sur les serveurs débutants, se prévient dès la conception.
| Donnée | Doit être validée où ? |
|---|---|
| Prix d'un objet | Toujours recalculé côté serveur, jamais reçu du client |
| Quantité déclarée par le client | Revérifiée contre l'état réel côté serveur |
| Fréquence d'un event | Limitée par un rate limiting explicite |
Piège dangereux
Faire confiance à une valeur envoyée par le client pour une décision économique (RegisterNetEvent('shop:buy', function(itemName, price) ... end)) est une faille critique : un client modifié peut envoyer n'importe quelle valeur, y compris un prix de zéro.
Le principe qui traverse tout ce cours, enfin détaillé
Depuis la leçon sur le modèle client/serveur, une idée revient constamment en filigrane : ne jamais faire confiance au client. Cette leçon la traite enfin de façon frontale et systématique, car c'est très probablement le sujet le plus important pour quiconque veut faire tourner un serveur RP public sans le voir ruiné par la triche en quelques jours.
Pourquoi le client ne peut jamais être fiable
Un client FiveM tourne entièrement sur une machine que le joueur contrôle. Des outils existent pour modifier le comportement du jeu localement, intercepter et altérer les données envoyées, ou simuler des actions qui n'ont jamais réellement eu lieu dans le jeu. Concrètement, cela signifie qu'un event serveur peut recevoir absolument N'IMPORTE QUELLE valeur d'un client malveillant — y compris des valeurs qu'un client "honnête" n'enverrait jamais.
Toujours recalculer, jamais recevoir
La règle pratique qui en découle est simple à énoncer mais facile à oublier en pratique : toute donnée qui a une conséquence économique ou de gameplay réelle (un prix, une quantité, des dégâts) doit être RECALCULÉE ou relue depuis une source fiable côté serveur, jamais simplement acceptée telle quelle depuis ce que le client a envoyé.
Le rate limiting, une protection complémentaire
Même une fois les valeurs validées, un client peut chercher à exploiter une faille en déclenchant un event en boucle très rapidement (spam), par exemple pour tenter de dupliquer une action avant que le serveur n'ait eu le temps de mettre à jour son état. Limiter la fréquence à laquelle un même joueur peut déclencher un event précis ferme cette classe entière de vulnérabilités.
Une détection qui reste probabiliste
Les vérifications de plausibilité (comme une distance parcourue physiquement impossible en un temps donné) ne prouvent jamais une triche avec une certitude absolue — un pic de latence peut aussi en être la cause. C'est pour cela qu'on privilégie le logging systématique des cas suspects plutôt qu'une sanction automatique instantanée, laissant à un humain la décision finale.
Commandes & code
Sécurité : ne jamais faire confiance au client
Un client FiveM peut être modifié (mods, injections). Toute décision qui affecte le jeu doit être revalidée côté serveur.
-- MAUVAIS : faire confiance au client pour le prix envoyé
RegisterNetEvent('shop:buy', function(itemName, price)
-- le client peut envoyer n'importe quelle valeur, ex: price = 0
RemoveMoney(source, price) -- FAILLE CRITIQUE : le joueur choisit son propre prix
AddItem(source, itemName, 1)
end)-- BON : le serveur est la SEULE source de vérité pour les prix et les règles
RegisterNetEvent('shop:buy', function(itemName)
local src = source
local itemConfig = Config.Items[itemName]
if not itemConfig then return end -- item inexistant, on ignore silencieusement
local price = Config.ShopPrices[itemName] -- prix lu côté serveur, jamais depuis le client
if not price then return end
local playerMoney = GetPlayerMoney(src)
if playerMoney < price then
TriggerClientEvent('technologik:notify', src, 'Fonds insuffisants')
return
end
RemoveMoney(src, price)
AddItem(src, itemName, 1)
end)-- Rate limiting basique contre le spam d'events (protection anti-exploit générique)
local lastTrigger = {}
local function IsRateLimited(source, eventName, cooldownMs)
local key = source .. ':' .. eventName
local now = GetGameTimer()
if lastTrigger[key] and (now - lastTrigger[key]) < cooldownMs then
return true
end
lastTrigger[key] = now
return false
end
RegisterNetEvent('shop:buy', function(itemName)
local src = source
if IsRateLimited(src, 'shop:buy', 500) then
print(('[SECURITY] %s spam shop:buy'):format(src))
return
end
-- ... logique d'achat validée
end)-- Validation stricte des types et des bornes de valeur
RegisterNetEvent('technologik:setWaypoint', function(x, y)
if type(x) ~= 'number' or type(y) ~= 'number' then return end
if x < -8000.0 or x > 8000.0 or y < -8000.0 or y > 8000.0 then return end -- hors carte = suspect
-- ...
end)
-- Détection basique de déplacement impossible (speedhack / téléportation)
local lastCoords = {}
AddEventHandler('playerSpawned', function()
lastCoords[source] = GetEntityCoords(GetPlayerPed(source))
end)
CreateThread(function()
while true do
Wait(2000)
for _, playerId in ipairs(GetPlayers()) do
local ped = GetPlayerPed(playerId)
local coords = GetEntityCoords(ped)
local last = lastCoords[playerId]
if last then
local distance = #(coords - last)
if distance > 300.0 then -- déplacement impossible en 2 secondes
print(('[ANTICHEAT] %s téléportation suspecte: %.1fm'):format(playerId, distance))
end
end
lastCoords[playerId] = coords
end
end
end)Résumé
- Toute donnée métier sensible (prix, quantité, dégâts) doit être décidée et lue côté serveur uniquement.
- Le rate limiting protège contre le spam d'events, souvent utilisé pour dupliquer des ressources.
- Une détection anti-triche basique (bornes, vitesse de déplacement) suffit à repérer une grande partie des abus courants.
- Logger systématiquement les tentatives suspectes facilite l'investigation après coup.
Exercices pratiques
Mission : colmater complètement la faille de la boutique
Objectif : Corriger une faille de prix côté client, ajouter un rate limiting, et raisonner sur les limites d'une détection anti-triche probabiliste.
Contexte
L'audit de sécurité de technologik_shop a permis de corriger le event shop:buy pour qu'il ne reçoive plus le prix depuis le client, seulement itemName. L'équipe pense le problème résolu, mais un joueur parvient encore à acheter des dizaines d'objets en quelques secondes en spammant le bouton d'achat depuis un client modifié.