20 maja 2026
5 min read
ZeroTrust Team

Zabezpiecz swoje zdarzenia: Poradnik bezpieczeństwa zdarzeń FiveM Lua

Cheaty umożliwiają złośliwym klientom wywoływanie zdarzeń w dowolnym kontekście. Dowiedz się, jak zabezpieczyć komunikację klient-serwer, wdrożyć walidację po stronie serwera i skonfigurować zmienne bezpieczeństwa (convars) FXServer.

Zespół ds. anti-cheata zawsze stara się go ulepszać, ale czasami coś się prześlizgnie.

W tym poradniku postaramy się omówić kilka powszechnych praktyk, które możesz wdrożyć, aby zwiększyć bezpieczeństwo swojego serwera poprzez odpowiednie zabezpieczenie zdarzeń.

Zrozumienie zdarzeń sieciowych w FiveM

Cheaty mogą pozwalać klientowi na wywoływanie zdarzeń w dowolnym kontekście.

Mówiąc o kontekście, mamy na myśli to, że mogą one wywołać relację klient->serwer (poprzez TriggerServerEvent) lub zasób klienta->zasób klienta (poprzez TriggerEvent).

Prawidłowe użycie obsługi zdarzeń w Lua

Podczas pracy ze zdarzeniami w Lua kluczowe jest ich prawidłowe rejestrowanie w zależności od tego, czy są wywoływane przez klienta, czy przez serwer.

Częstym błędem jest rejestrowanie zdarzeń serwerowych, które nie powinny być wywoływane przez klienta (lub odwrotnie), co może prowadzić do poważnych luk w zabezpieczeniach.

AddEventHandler

Używaj AddEventHandler, gdy zdarzenie ma być wywoływane w tym samym kontekście, czyli klient-klient lub serwer-serwer. Gwarantuje to, że zdarzenia nie są sieciowe i nie mogą być wywołane przez przeciwną stronę.

AddEventHandler("eventName", function(eventParam1, eventParam2)
    -- Kod tutaj zostanie wykonany, gdy zdarzenie zostanie wywołane w tym samym kontekście.
end)

RegisterNetEvent

Używaj RegisterNetEvent, gdy zdarzenie musi być wywoływane w różnych kontekstach, np. z klienta na serwer lub z serwera na klienta.

Pod maską

UWAGA: To nie blokuje wykonania z tego samego kontekstu. Pod maską RegisterNetEvent to wrapper, który jest po prostu następującym kodem: RegisterNetEvent("eventName") AddEventHandler("eventName", function() ... end)
RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- Kod tutaj zostanie wykonany, gdy zdarzenie zostanie wywołane w różnych kontekstach.
end)

Ten przykład dotyczy klienta, a jak wszystko po stronie klienta, nie jest to rozwiązanie niezawodne i może być zmanipulowane przez cheaterów.

Jeśli chcesz zablokować wykonanie z tego samego kontekstu (np. zapobiec wywołaniu przez klienta sieciowego zdarzenia typu serwer-serwer), powinieneś zarejestrować swoje zdarzenie i sprawdzić źródło nadawcy:

RegisterNetEvent("eventName", function(eventParam1, eventParam2)
    -- Serwer wyśle identyfikator sieciowy `65535` dla zdarzeń z serwera
    if source ~= 65535 then return end
end)

Dodawanie kontroli i weryfikacji

Nawet jeśli zbudujesz silny anti-cheat, dodanie kontroli w zdarzeniach serwerowych czyni je znacznie bezpieczniejszymi. Jest to wysoce zalecana praktyka, choć nie zapobiega wszystkiemu. Poniżej dzielimy się kilkoma dobrymi wskazówkami.

  • Pieniędzy gracza: Waliduj stan konta i transakcje po stronie serwera.
  • State bags gracza: Weryfikuj dane stanu w aktywnych sesjach.
  • Przedmiotów w ekwipunku gracza: Sprawdzaj nazwy i ilości przedmiotów.
  • Pozycji gracza: Weryfikuj zasięgi i odległości.
  • Doświadczenia i poziomu gracza: Upewnij się, że statystyki są obliczane po stronie serwera.
  • Uprawnień i ról gracza: Waliduj listy kontroli dostępu (ACL) po stronie serwera.

Złota zasada

Upewnij się, że pobierasz wszystkie wartości przy użyciu metod po stronie serwera, nie pozwalając graczom na podawanie ani modyfikowanie wartości. Pamiętaj, że kontrole po stronie klienta mogą być również dobrą praktyką dla UX (wrażeń użytkownika), ale można je łatwo obejść.

Zapewnia to integralność i bezpieczeństwo środowiska gry.

Przykłady typowych wzorców bezpieczeństwa

Wszystkie poniższe przykłady zakładają użycie jakiegoś frameworka (takiego jak ESX, QB-Core itp.).

Złe zabezpieczenie (Nigdy tego nie rób)

Ma to na celu pokazanie złych sposobów tworzenia zdarzeń, nigdy nie powinieneś tego robić. Bezpośrednie dodawanie przedmiotów użytkownikowi na podstawie jego własnych danych wejściowych jest zawsze złą praktyką: zawsze należy walidować dane wejściowe użytkownika.

RegisterNetEvent("job:givePlayerItem", function(item, count)
    local ply = FX.GetPlayerFromSource(source)
    -- Bezpośrednie dodawanie przedmiotów użytkownikowi na podstawie jego własnych danych wejściowych jest niebezpieczne!
    ply.addItem(item, count)
end)

Dobre zabezpieczenie (Zalecane)

Oto solidny wzorzec bezpieczeństwa. Serwer śledzi stan i koordynaty gracza oraz weryfikuje działania za pomocą ticków po stronie serwera, zamiast polegać wyłącznie na wyzwalaczach klienta.

-- losowa koordynata
local VALID_JOB_COORD = vector3(125.0, 111.1, 35.83)
local MAX_ITEM_COUNT = 10

-- losowa koordynata
local VALID_TURNIN_COORD = vector3(1888.0, 1254.1, 48.0)

local ITEM_NAME = 'log'

-- lista graczy z aktywnymi pracami
local activeJobs = {}

AddEventHandler("playerDropped", function ()
    if not activeJobs[source] then return end
    activeJobs[source] = nil
end)

function isPedWithinRange(ped, tgtCoords)
    return #(GetEntityCoords(ped) - tgtCoords) < 15.0
end

-- obsługa zwiększania ilości przedmiotów po stronie serwera
CreateThread(function()
    while true do
        for src, data in pairs(activeJobs) do
            local ped = GetPlayerPed(src)
            -- jeśli nie są w zasięgu, nie chcemy dawać im przedmiotu
            if isPedWithinRange(ped, VALID_JOB_COORD) then
                -- daj im przedmiot, ale ogranicz go do MAX_ITEM_COUNT
                data.itemCount = math.min(data.itemCount + 1, MAX_ITEM_COUNT)
            end
        end
        -- przetwarzaj tick pracy raz na sekundę
        Wait(1000)
    end
end)

RegisterNetEvent("job:startJob", function()
    local ped = GetPlayerPed(source)
    -- jeśli są w odległości do 15 jednostek, wykonują pracę
    if isPedWithinRange(ped, VALID_JOB_COORD) then
        activeJobs[source] = {
            itemCount = 0,
        }
    end
end)

RegisterNetEvent("job:givePlayerItem", function()
    local ply = FX.GetPlayerFromSource(source)
    -- jeśli nie mają aktywnej pracy, nie powinni tego wywoływać!
    local jobData = activeJobs[source]
    if not jobData then return end

    local ped = GetPlayerPed(source)
    -- nie są w zasięgu koordynatów oddawania, odrzuć ich zmiany
    if not isPedWithinRange(ped, VALID_TURNIN_COORD) then return end

    -- zresetuj dane przedmiotu, aby nie mogli go wywołać wielokrotnie
    activeJobs[source] = nil

    -- dodaj przedmioty użytkownikowi po walidacji serwerowej
    ply.addItem(ITEM_NAME, jobData.itemCount)
end)

Opcje właściciela serwera (Zmienne Convars)

Należy pamiętać, że poniższych ustawień nie powinno się modyfikować, chyba że dokładnie wiesz, co robisz. Zespół Cfx.re / Adhesive zawsze ciężko pracuje, aby zapobiegać cheaterom. Większość z tych funkcji będzie domyślnie włączona w wersjach kompilacji FXServer 8450 i wyższych.

  • sv_kick_players_cnl_timeout_sec: Limit czasu, po którym serwer wyrzuci gracza (np. jeśli wynosi 600, wyrzuci go po 10 minutach braku połączenia z CnL).
  • sv_kick_players_cnl_update_rate_sec: Częstotliwość, z jaką CnL jest odpytywany o listę graczy.
  • sv_pure_verify_client_settings: Zastępuje okresowe zapytania do info.json na kliencie. Ustanawia bezpieczne połączenie między adhesive a svadhesive i weryfikuje niektóre parametry sv_settings, takie jak pureLevel, scripthook i inne.
  • sv_kick_players_cnl_consecutive_failures: Ile kolejnych niepowodzeń po timeout_sec jest wymaganych do wyrzucenia gracza. Domyślnie ustawiono 2, co oznacza, że jeśli gracz nie zamelduje się przez 10 minut, a następnie opuści kolejną aktualizację logowania, zostanie wyrzucony. Działa to jako mechanizm zabezpieczający.
  • sv_authMaxVariance: Wariancja określa, jak prawdopodobne jest, że identyfikator użytkownika ulegnie zmianie u danego dostawcy (tj. 'steam', 'ip' lub 'license').
  • sv_authMinTrust: Zaufanie określa, jak mało prawdopodobne jest, że tożsamość użytkownika zostanie sfałszowana przez złośliwego klienta.
  • sv_filterRequestControl: Zmienna konsoli używana do blokowania routingu REQUEST_CONTROL_EVENT na podstawie konfigurowalnej polityki.
  • sv_disableClientReplays: Włączenie tej opcji ma na celu zmniejszenie szans na opcje cheatowania. Pamiętaj, że wyłączy to Rockstar Editor.

Wyniki dla gracza

Aktywowanie tych zmiennych convar prawdopodobnie spowoduje wyrzucenie gracza z następującym powodem: Connection to CNL timed out.

Wyrzucenie nie oznacza, że gracz automatycznie otrzyma Globalnego Bana. Zapewnia jednak silną wskazówkę dotyczącą niezawodności gracza, co jest niezwykle przydatne przy ocenie jego wiarygodności.

Warto wiedzieć

Dostarczone kody nie są przeznaczone do bezmyślnego kopiowania i wklejania. To tylko kilka wskazówek, jak zapobiegać niektórym działaniom, które mogą mieć miejsce na serwerze. Wymaga to pewnej wiedzy programistycznej. Zawsze możesz dołączyć do naszego Discorda, aby uzyskać dodatkową pomoc.