Proteggere gli eventi del server FiveM: ferma i cheater dall'abusare degli eventi di rete
I cheater attivano i tuoi eventi server con qualsiasi valore. Come convalidare sul server: usa source, controlla la distanza, il job e i cooldown, e mantieni i prezzi in una tabella del server.
Uno script di negozio su molti server sembra questo, ed è un bottone di soldi gratuiti:
TriggerServerEvent('shop:buy', 'weapon_pistol', 1, -999999)Chiunque abbia un menu di cheat può inviare quella riga. Il server esegue l'handler, prende un prezzo negativo dal giocatore e felicemente aggiunge i soldi. Questa guida mostra come scrivere eventi server che non fidano del client.
La regola: il client non è fidato
Tutto ciò che arriva in un evento di rete viene dalla macchina di un giocatore, che quel giocatore controlla. Tratta ogni argomento come input utente su un sito web: può essere mancante, il tipo sbagliato, negativo, enorme o semplicemente inventato.
Cosa il server può fidare:
source, il giocatore che ha attivato l'evento. Il motore lo imposta.- I dati che il server già tiene: il job del giocatore, l'inventario, i soldi, la posizione, e le tue stesse tabelle di config.
Cosa non deve mai fidare: quantità, prezzi, nomi di articoli, id di giocatori, coordinate, nomi di job e flag « success » inviati dal client.
Un cattivo esempio
RegisterNetEvent('shop:buy', function(item, count, price)
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.removeMoney(price * count)
xPlayer.addInventoryItem(item, count)
end)Il client sceglie l'articolo, la quantità e il prezzo. Un cheater compra qualsiasi cosa per nulla, o una quantità negativa per stampare soldi.
Un buon esempio
Mantieni i prezzi sul server, convalida i tipi, controlla dove è il giocatore, poi agisci.
local Items = {
water = { price = 5, max = 10 },
bread = { price = 8, max = 10 },
}
local shopCoords = vec3(25.7, -1347.3, 29.5)
local lastBuy = {}
RegisterNetEvent('shop:buy', function(item, count)
local src = source
-- 1. types and ranges
if type(item) ~= 'string' or type(count) ~= 'number' then return end
count = math.floor(count)
local def = Items[item]
if not def or count < 1 or count > def.max then return end
-- 2. cooldown
local now = GetGameTimer()
if lastBuy[src] and now - lastBuy[src] < 1000 then return end
lastBuy[src] = now
-- 3. distance
local ped = GetPlayerPed(src)
if #(GetEntityCoords(ped) - shopCoords) > 5.0 then return end
-- 4. price comes from the server table
local total = def.price * count
local xPlayer = ESX.GetPlayerFromId(src)
if not xPlayer or xPlayer.getMoney() < total then return end
xPlayer.removeMoney(total)
xPlayer.addInventoryItem(item, count)
end)
AddEventHandler('playerDropped', function()
lastBuy[source] = nil
end)GetEntityCoords(GetPlayerPed(source)) sul server ha bisogno di OneSync abilitato. Se non l'hai ancora fatto, vedi abilitare OneSync.
Lo stesso su QBCore e QBox
Solo la ricerca del giocatore, il campo job e la chiamata di soldi cambiano. La convalida rimane la stessa.
-- QBCore
local QBCore = exports['qb-core']:GetCoreObject()
local Player = QBCore.Functions.GetPlayer(src)
if not Player or Player.PlayerData.money.cash < total then return end
Player.Functions.RemoveMoney('cash', total, 'shop-purchase')-- QBox
local Player = exports.qbx_core:GetPlayer(src)
if not Player or Player.PlayerData.money.cash < total then return end
Player.Functions.RemoveMoney('cash', total, 'shop-purchase')Controlli di job e permesso
Se un evento dovrebbe funzionare solo per un job, controllalo sul server dai dati del framework, non da un argomento.
-- ESX
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer or xPlayer.job.name ~= 'police' then return end-- QBCore
local Player = QBCore.Functions.GetPlayer(source)
if not Player or Player.PlayerData.job.name ~= 'police' then return endPer le azioni di admin, controlla un permesso ACE invece di un flag client. Vedi permessi ACE:
if not IsPlayerAceAllowed(source, 'command.myadmin') then return endLe ricompense hanno bisogno di prova lato server
Eventi come job:finished o mission:reward sono il classico bersaglio. Un cheater semplicemente li attiva in un loop.
- Mantieni l'importo della ricompensa in una tabella del server, mai negli argomenti dell'evento.
- Ricorda sul server che il giocatore ha iniziato il job, e pagalo solo se quello stato esiste. Cancellalo quando paghi.
- Aggiungi un tempo minimo tra l'inizio e la fine.
- Controlla che il giocatore sia vicino al punto di consegna quando finisce.
local activeJobs = {}
RegisterNetEvent('job:start', function()
activeJobs[source] = GetGameTimer()
end)
RegisterNetEvent('job:finish', function()
local src = source
local startedAt = activeJobs[src]
if not startedAt or GetGameTimer() - startedAt < 30000 then return end
activeJobs[src] = nil
-- pay a fixed amount defined on the server
end)Altre abitudini che aiutano
- Non usare mai l'id del giocatore dal client. Usa sempre
source. Un evento comegiveMoney(playerId, amount)permette a un cheater di pagare chiunque. - Non esporre eventi di cui non hai bisogno. Un evento registrato solo con
AddEventHandler, senzaRegisterNetEvent, non può essere attivato da un client. - Non eseguire stringhe client come codice. Non passare mai un valore client a
load,ExecuteCommando una stringa SQL. Usa query parametrizzate, come nella guida oxmysql. - Registra i casi strani. Quando un controllo fallisce, stampa il giocatore e il nome dell'evento. Un modello di fallimenti è come trovi i cheater. Banna da txAdmin, vedi moderating with txAdmin.
Attenzione: nascondere il nome dell'evento, offuscarlo o aggiungere un « token » lato client non lo protegge. Il client può leggere tutto quello. Solo i controlli sul server contano.
Checklist
| Sintomo | Soluzione |
|---|---|
| Il client invia il prezzo o la quantità | Mantieni una tabella di prezzi sul server e calcola il totale lì |
| Il client invia un id di giocatore | Usa source invece |
| Il nome dell'articolo viene dal client | Accetta solo nomi trovati in una tabella del server |
| Quantità negative o enormi | Controlla type, math.floor, e un minimo e un massimo |
| Evento attivato da tutta la mappa | Confronta GetEntityCoords(GetPlayerPed(source)) con la posizione del bersaglio |
| Evento spammato in un loop | Per-player cooldown, cancellato in playerDropped |
| Azione solo job | Leggi il job dal framework sul server |
| Evento di ricompensa abusato | Memorizz lo stato del job sul server e paghi un importo fisso una volta |
Risposte rapide
Un giocatore può veramente attivare i miei eventi server?
Sì. Qualsiasi evento registrato con RegisterNetEvent può essere attivato da un client modificato con qualsiasi argomento. Il server non può dire una vera chiamata di script da una falsa, quindi deve convalidare tutto.
È sicuro usare un id di giocatore inviato dal client?
No. Sul server, source è impostato dal motore e non può essere falsificato. Un id di giocatore, nome o identificatore inviato come argomento può esserlo, quindi non lo usare mai per decidere chi viene pagato o punito.
Ho bisogno di un anticheat se convalido i miei eventi?
La convalida è la base e ferma la maggior parte dell'abuso dei tuoi script. Un anticheat aggiunge rilevamento per cose che gli eventi non possono vedere, come menu iniettati. Vedi [FiveM anticheat basics](/blog/fivem-anticheat-basics).
Script senza questo problema
Shop CreatorCrea un negozio in meno di un minuto — proprietari, dipendenti, casseforti e rapine inclusi.Vedi script →
Item Creator V2Crea oggetti utilizzabili con animazioni, props, effetti e altro — senza scrivere codice.Vedi script →
Advanced BoostingBoosting di veicoli dal tablet: contratti dalla classe D alla S+, crew e coda in tempo reale.Vedi script →