Sichere FiveM Server-Events: Stoppe Betrüger davon ab, Net-Events zu missbrauchen
Betrüger triggern deine Server-Events mit beliebigen Werten. Wie man auf dem Server validiert: source verwenden, Distanz, Job und Cooldowns überprüfen, und Preise in einer Server-Tabelle halten.
Ein Shop-Script auf vielen Servern sieht so aus, und es ist ein kostenloser Geld-Button:
TriggerServerEvent('shop:buy', 'weapon_pistol', 1, -999999)Jeder mit einem Cheat-Menü kann diese Zeile senden. Der Server läuft den Handler, nimmt einen negativen Preis vom Spieler und addiert glücklich das Geld. Dieser Leitfaden zeigt, wie man Server-Events schreibt, die den Client nicht vertrauen.
Die Regel: Der Client wird nicht vertraut
Alles, das in einem Net-Event ankommt, kommt von der Maschine eines Spielers, die dieser Spieler kontrolliert. Behandle jedes Argument als Benutzereingabe auf einer Website: Es kann fehlen, den falschen Typ haben, negativ sein, riesig oder einfach erfunden.
Was der Server kann vertrauen:
source, der Spieler, der das Event ausgelöst hat. Das Engine setzt es.- Daten, die der Server bereits hält: Der Job des Spielers, Inventar, Geld, Position, und deine eigenen Config-Tabellen.
Was es niemals vertrauen darf: Mengen, Preise, Item-Namen, Spieler-IDs, Koordinaten, Job-Namen und „Erfolgs"-Flaggen, die vom Client gesendet werden.
Ein schlechtes Beispiel
RegisterNetEvent('shop:buy', function(item, count, price)
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.removeMoney(price * count)
xPlayer.addInventoryItem(item, count)
end)Der Client wählt das Item, die Anzahl und den Preis. Ein Betrüger kauft alles kostenlos, oder eine negative Anzahl, um Geld zu drucken.
Ein gutes Beispiel
Behalte Preise auf dem Server, validiere die Typen, überprüfe, wo der Spieler ist, dann handle.
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)) auf dem Server benötigt OneSync aktiviert. Wenn du das noch nicht getan hast, siehe OneSync aktivieren.
Das gleiche in QBCore und QBox
Nur der Spieler-Lookup, das Job-Feld und der Geld-Anruf ändern sich. Die Validierung bleibt gleich.
-- 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')Job- und Berechtigungsprüfungen
Wenn ein Event nur für einen Job funktionieren sollte, überprüfe es auf dem Server aus den Framework-Daten, nicht aus einem Argument.
-- 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 endFür Admin-Aktionen überprüfe stattdessen eine ACE-Berechtigung. Siehe ACE-Berechtigungen:
if not IsPlayerAceAllowed(source, 'command.myadmin') then return endBelohnungen benötigen Server-seitigen Beweis
Events wie job:finished oder mission:reward sind das klassische Ziel. Ein Betrüger einfach feuert sie in einer Schleife ab.
- Behalte den Belohnungsbetrag in einer Server-Tabelle, nie in den Event-Argumenten.
- Denke auf dem Server daran, dass der Spieler den Job gestartet hat, und zahle nur, wenn dieser Zustand existiert. Lösche ihn, wenn du zahlst.
- Füge eine Mindestzeit zwischen Start und Ende hinzu.
- Überprüfe, dass der Spieler am Lieferpunkt ist, wenn er endet.
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)Andere Gewohnheiten, die helfen
- Verwende niemals die Spieler-ID des Clients. Verwende immer
source. Ein Event wiegiveMoney(playerId, amount)lässt einen Betrüger jeden bezahlen. - Exponiere nicht Events, die du nicht brauchst. Ein Event, das nur mit
AddEventHandlerregistriert ist, ohneRegisterNetEvent, kann nicht von einem Client ausgelöst werden. - Führe Client-Zeichenketten nicht als Code aus. Übergebe niemals einen Client-Wert an
load,ExecuteCommandoder eine SQL-Zeichenkette. Verwende parametrisierte Abfragen, wie im oxmysql-Handbuch. - Protokolliere die seltsamen Fälle. Wenn eine Überprüfung fehlschlägt, drucke den Spieler und den Event-Namen. Ein Muster von Fehlschlägen ist, wie du Betrüger findest. Ban von txAdmin, siehe Moderieren mit txAdmin.
Achtung: Das Verstecken des Event-Namens, das Obfuskieren oder das Hinzufügen eines Client-seitigen „Token" schützt es nicht. Der Client kann das alles lesen. Nur Überprüfungen auf dem Server zählen.
Checkliste
| Symptom | Behebung |
|---|---|
| Client sendet den Preis oder die Menge | Behalte eine Preistabelle auf dem Server und berechne die Gesamtsumme dort |
| Client sendet eine Spieler-ID | Verwende stattdessen source |
| Item-Name kommt vom Client | Akzeptiere nur Namen, die in einer Server-Tabelle gefunden werden |
| Negative oder riesige Mengen | Überprüfe type, math.floor, und ein Minimum und Maximum |
| Event von über die Karte hinweg ausgelöst | Vergleiche GetEntityCoords(GetPlayerPed(source)) mit der Zielposition |
| Event in einer Schleife gespammt | Pro-Spieler-Cooldown, gelöscht in playerDropped |
| Nur Job-Aktion | Lese den Job vom Framework auf dem Server |
| Belohnungs-Event missbraucht | Speichere Job-Zustand auf dem Server und zahle einen festgelegten Betrag einmal |
Kurze Antworten
Kann ein Spieler wirklich meine Server-Events triggern?
Ja. Jedes Event, das mit RegisterNetEvent registriert ist, kann von einem modifizierten Client mit beliebigen Argumenten ausgelöst werden. Der Server kann einen echten Script-Aufruf von einem gefälschten nicht unterscheiden, daher muss er alles validieren.
Ist es sicher, eine Spieler-ID zu verwenden, die der Client sendet?
Nein. Auf dem Server wird source vom Engine gesetzt und kann nicht gefälscht werden. Eine Spieler-ID, ein Name oder ein Identifier, der als Argument gesendet wird, kann es, daher verwende ihn niemals, um zu entscheiden, wer bezahlt oder bestraft wird.
Brauche ich einen Anticheat, wenn ich meine Events validiere?
Validierung ist die Grundlage und stoppt den meisten Missbrauch deiner eigenen Scripts. Ein Anticheat fügt Erkennung für Dinge hinzu, die Events nicht sehen können, wie injizierte Menüs. Siehe [FiveM Anticheat Grundlagen](/blog/fivem-anticheat-basics).
Scripts ohne dieses Problem
Shop CreatorBau einen Shop in unter einer Minute — Besitzer, Angestellte, Tresore und Überfälle inklusive.Script ansehen →
Item Creator V2Erstelle nutzbare Items mit Animationen, Props, Effekten und mehr — ganz ohne Code.Script ansehen →
Advanced BoostingFahrzeug-Boosting per Tablet: Aufträge von Klasse D bis S+, Crews und eine Live-Warteschlange.Script ansehen →