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:

text
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

lua
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.

lua
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.

lua
-- 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')
lua
-- 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.

lua
-- ESX
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer or xPlayer.job.name ~= 'police' then return end
lua
-- QBCore
local Player = QBCore.Functions.GetPlayer(source)
if not Player or Player.PlayerData.job.name ~= 'police' then return end

Für Admin-Aktionen überprüfe stattdessen eine ACE-Berechtigung. Siehe ACE-Berechtigungen:

lua
if not IsPlayerAceAllowed(source, 'command.myadmin') then return end

Belohnungen 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.
lua
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 wie giveMoney(playerId, amount) lässt einen Betrüger jeden bezahlen.
  • Exponiere nicht Events, die du nicht brauchst. Ein Event, das nur mit AddEventHandler registriert ist, ohne RegisterNetEvent, kann nicht von einem Client ausgelöst werden.
  • Führe Client-Zeichenketten nicht als Code aus. Übergebe niemals einen Client-Wert an load, ExecuteCommand oder 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 →

Weiterlesen