Zawsze, gdy klient ma możliwość wywołania akcji na serwerze, istnieje potencjał nadużyć. Chociaż RemoteEvents i RemoteFunctions są najczęstszymi wektorami ataków w tym przypadku, inne instancje, takie jak ProximityPrompt, są podatne na ataki. Ta sekcja omawia, jak zabezpieczyć tę granicę.
Walidacja zdalnych danych wejściowych
Każda część danych wysyłanych z klienta musi być zweryfikowana przez serwer przed jej użyciem. Zastosuj te warstwy walidacji w zależności od tego, co robi zdalna funkcja.
Walidacja kontekstu / uprawnień
Jest to konieczne dla zdalnych akcji, które wpływają na stan doświadczenia, postęp lub innych graczy. Serwer musi zweryfikować, czy gracz ma niezbędne uprawnienia do złożenia prośby. Na przykład, czy gracz jest wystarczająco blisko sklepu, aby coś kupić? Czy mają klucz potrzebny do otwarcia drzwi? Czy ich postać jest żywa?
Walidacja jest konieczna dla zdalnych akcji, które:
- Dają nagrody lub modyfikują postęp gracza
- Wpływają na innych graczy lub wspólny stan doświadczenia
- Wykonują działania z wymaganiami dotyczącymi odległości, czasu lub uprawnień (na przykład, odległość do sklepu)
Walidacja typu i struktury
Innym atakiem, który mogą zastosować oszuści, jest wysłanie technicznie poprawnych typów, ale w skrajnych rozmiarach, długościach lub w inny sposób uszkodzonych. Na przykład, jeśli serwer musi wykonać kosztowną operację na ciągu, której czas wykonania rośnie wraz z jego długością, oszust może wysłać niesamowicie duży lub uszkodzony ciąg, aby spowolnić serwer.
Innym powszechnym atakiem, który mogą zastosować oszuści, jest wysłanie tables zamiast Instance. Złożone ładunki mogą naśladować to, co byłoby w przeciwnym razie zwykłym odwołaniem do obiektu.
Na przykład, w przypadku dostępu do systemu sklepu w doświadczeniu, gdzie dane przedmiotów, takie jak ceny, są przechowywane w obiektach NumberValue, oszust może obejść wszystkie inne kontrole, wykonując następujące:
LocalScript w StarterPlayerScriptslocal ReplicatedStorage = game:GetService("ReplicatedStorage")local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")local payload = {Name = "Ultra Blade",ClassName = "Folder",Parent = itemDataFolder,Price = {Name = "Price",ClassName = "NumberValue",Value = 0, -- Ujemne wartości mogą być również używane, co skutkuje przyznaniem waluty zamiast jej odebrania!},}-- Wyślij złośliwy ładunek do serwera (zostanie odrzucony)print(buyItemEvent:InvokeServer(payload)) -- Wypisuje "false Invalid item provided"-- Wyślij prawdziwy przedmiot do serwera (to przejdzie!)print(buyItemEvent:InvokeServer(itemDatafolder["Real Blade"])) -- Wypisuje "true" oraz pozostałą walutę, jeśli zakup się powiódł
Script w ServerScriptStorage
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- Sprawdź, czy przekazany przedmiot nie jest oszustwem i jest w folderze ItemData
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- Serwer może następnie zrealizować zakup na podstawie poniższego przykładowego przepływu
end
-- Powiąż "buyItem()" z wywołaniem zwrotnym zdalnej funkcji
buyItemEvent.OnServerInvoke = buyItem
Walidacja wartości
Oprócz walidacji typów i danych, powinieneś walidować wartości przesyłane przez RemoteEvents i RemoteFunctions, upewniając się, że są one poprawne i logiczne w kontekście, który jest żądany. Jest to niezbędne dla zdalnych akcji, które obsługują walutę, przedmioty, dane liczbowe lub treści generowane przez użytkowników. Musisz upewnić się, że wartości są w oczekiwanych granicach (np. ilość zakupów jest większa od zera) i że podane identyfikatory lub nazwy są ważne (np. itemId odpowiada prawdziwemu przedmiotowi w Twojej grze).
Specjalne uwagi dotyczące wartości liczbowych: Zarówno inf, jak i NaN są poprawnymi typami liczbowymi, ale oba mogą powodować poważne problemy, jeśli oszust je wyśle i nie zostaną one obsłużone poprawnie. Użyj funkcji takich jak te, aby je wykryć i odrzucić:
local function isNaN(n: number): boolean
-- NaN nigdy nie jest równe sobie
return n ~= n
end
local function isInf(n: number): boolean
-- Liczba może być -inf lub +inf
return math.abs(n) == math.huge
end
Niebezpieczeństwo NaN
Oszust może wysłać NaN (Not a Number) jako argument. NaN jest wyjątkowo niebezpieczne, ponieważ jest typu "number", ale nie przechodzi żadnych standardowych porównań, co pozwala mu subtelnie omijać logiczne kontrole, które wydają się bezpieczne. Przyjrzyjmy się podatnemu systemowi handlowemu, gdzie gracz składa ofertę.
-- PODEJŚCIE SERWERA PODEJŚCIE
local function onCreateTradeOffer(player, offeredGold)
-- 1. WALIDACJA TYPU: To działa! typeof(NaN) to "number".
if typeof(offeredGold) ~= "number" then
return "Invalid offer"
end
-- 2. WALIDACJA ZAKRESU: To omija kontrole!
-- (NaN < 0) jest fałszywe. (NaN > 1000000) jest również fałszywe. Kontrola nic nie robi.
if offeredGold < 0 or offeredGold > 1000000 then
return "Offer out of range"
end
-- 3. WALIDACJA INWENTARZA: To omija kontrole!
-- (NaN > player.Gold.Value) jest fałszywe.
if offeredGold > player.Gold.Value then
return "Not enough gold"
end
-- WRAŻLIWOŚĆ: Stworzono fałszywą ofertę handlową z NaN gold!
createTrade(player, {gold = offeredGold})
return "Trade offer created."
end
Oszust skutecznie stworzył ofertę handlową, nie posiadając żadnego złota, ponieważ każda kontrola milcząco zawiodła. Ponadto ta pojedyncza wartość NaN teraz zainfekuje każdy inny system, który spróbuje jej użyć i może nawet rozprzestrzenić się na innych graczy poprzez ofertę.
Poniżej znajdują się niektóre przykłady wysokiego poziomu dotyczące zabezpieczonego projektowania powszechnych funkcji w grze.
Sklep w doświadczeniu
Rozważ system sklepu w doświadczeniu z interfejsem użytkownika, na przykład menu wyboru produktów z przyciskiem "Kup". Gdy przycisk jest wciśnięty, możesz wywołać RemoteFunction między klientem a serwerem, aby złożyć prośbę o zakup. Ważne jest, aby serwer, będący najpewniejszym zarządcą doświadczenia, potwierdził, że użytkownik ma wystarczającą ilość pieniędzy, aby kupić przedmiot.

Celowanie w broń
Scenariusze walki wymagają szczególnej uwagi przy walidacji wartości, szczególnie przez walidację celowania i trafienia.
Wyobraź sobie doświadczenie, w którym gracz może wystrzelić wiązkę lasera w innego gracza. Zamiast tego, aby klient informował serwer, kogo zadać obrażenia, powinien zamiast tego informować serwer o pozycji początkowej strzału oraz o części/pozycji, którą uważa za cel. Serwer może następnie zweryfikować następujące:
- Pozycja, z której klient zgłasza strzał, znajduje się blisko postaci gracza na serwerze. Należy zauważyć, że serwer i klient mogą się nieznacznie różnić z powodu opóźnienia, więc konieczne będzie zastosowanie dodatkowej tolerancji.
- Pozycja, w którą klient zgłasza, że trafił, jest razonowo bliska pozycji części, którą klient zgłasza jako trafioną, na serwerze.
- Między pozycją, z której klient zgłasza strzał, a pozycją, w którą zgłasza trafienie, nie ma żadnych statycznych przeszkód. Ta kontrola zapewnia, że klient nie próbuje strzelać przez ściany. Należy zauważyć, że powinno to sprawdzać tylko statyczną geometrię, aby uniknąć odrzucania legalnych strzałów z powodu opóźnienia.
Dodatkowo, możesz chcieć wdrożyć dalsze walidacje po stronie serwera w następujący sposób:
- Śledzić, kiedy gracz ostatnio strzelał ze swojej broni i zweryfikować, czy nie strzela zbyt szybko.
- Śledzić ilość amunicji każdego gracza na serwerze i potwierdzić, że strzelający gracz ma wystarczającą ilość amunicji, aby wykonać atak bronią.
- Jeśli wdrożyłeś drużyny lub system walki "gracze przeciwko botom", potwierdzić, że postać, która została trafiona, jest wrogiem, a nie kolegą.
- Potwierdzić, że trafiony gracz jest żywy.
- Przechowywać stan broni i gracza na serwerze i potwierdzić, że strzelający gracz nie jest zablokowany przez bieżącą akcję, taką jak przeładowanie lub stan, jak sprint.
Ograniczenie prędkości
Zawsze, gdy logika po stronie serwera może być wywołana przez klienta (za pomocą zdalnych, zdarzeń Touched, wskazówek z bliskiego zasięgu, ClickDetectors itp.), istnieje możliwość, że logika ta może być spamowana przez oszustów lub nawet legalnych użytkowników. Ograniczenie prędkości kontroluje, jak często te działania mogą być wykonywane, zapobiegając nadużyciom i przeciążeniu systemu. Chociaż jest praktyczne (a czasami konieczne) wprowadzenie ograniczeń prędkości po stronie klienta, nigdy nie polegaj tylko na ograniczeniu prędkości po stronie klienta.
Dla każdej logiki po stronie serwera, która może być wywołana przez klienta, zawsze rozważ maksymalną prędkość, z jaką taka logika powinna działać. Wiele akcji po stronie serwera musi mieć swoje częstotliwości ograniczone, aby zapobiec nadużyciom lub niepowodzeniom, takich jak:
- Wywołania API Roblox: Backendowe API, takie jak DataStoreService i BadgeService, mają wbudowane ograniczenia prędkości, które spowodują, że Twoje żądania zakończą się niepowodzeniem, jeśli zostaną przekroczone.
- Kosztowne operacje obliczeniowe: Działania zużywające znaczące zasoby serwera lub wpływające na innych klientów.
- Przykład serwera: klonowanie dużych modeli z ServerStorage, nawet jeśli nigdy nie są replikowane do klientów.
- Przykład klienta: instrukcje dla wszystkich podłączonych klientów do jednoczesnej aktualizacji swojego GUI.
- Mechanika podatna na exploity: Działania, które mogą być nadużywane, jeśli wykonywane szybko, takie jak przyznawanie nietykalności, teleportowanie graczy lub nagradzanie walutą.
Przykład kubła z tokenami
Solidne i powszechne podejście do ograniczania prędkości to algorytm kubła z tokenami. Wyobraź sobie, że każdy użytkownik ma kubeł, który może pomieścić określoną liczbę tokenów. Aby wykonać jakąś akcję, użytkownik musi wydać token. Kubeł napełnia się nowymi tokenami w stałym tempie. Ta metoda pozwala na krótkie wybuchy działań, gdy jest to potrzebne, ale zapobiega długotrwałemu spamowi poprzez egzekwowanie średniego wskaźnika w czasie.
--!strict
-- Moduł zlokalizowany w ServerScriptService
type UserId = number
type Bucket = {
tokens: number,
last: number,
}
type TokenBucketT = {
capacity: number,
refillPerSecond: number,
buckets: { [UserId]: Bucket },
allow: (self: TokenBucketT, userId: UserId) -> boolean,
}
local TokenBucket = {}
TokenBucket.__index = TokenBucket
-- Tworzy limiter, który pozwala na maksymalnie `capacity` zdarzeń, napełniając go z powrotem do
-- pełna odpowiednią prędkością `capacity / windowSeconds` tokenów na sekundę.
function TokenBucket.new(capacity: number, windowSeconds: number): TokenBucketT
assert(capacity >= 1, "capacity must be >= 1")
assert(windowSeconds > 0, "windowSeconds must be > 0")
local self: TokenBucketT = {
capacity = capacity,
refillPerSecond = capacity / windowSeconds,
buckets = {},
}
return (setmetatable(self, TokenBucket) :: any) :: TokenBucketT
end
local function refill(b: Bucket, now: number, cap: number, rate: number)
local elapsed = now - b.last
if elapsed > 0 then
b.tokens = math.min(cap, b.tokens + elapsed * rate)
b.last = now
end
end
-- Zwraca fałsz, jeśli użytkownik przekroczy swoje ograniczenia
function TokenBucket:allow(userId: UserId): boolean
local now = time()
local b = self.buckets[userId]
if not b then
b = { tokens = self.capacity, last = now }
self.buckets[userId] = b
else
refill(b, now, self.capacity, self.refillPerSecond)
end
if b.tokens >= 1 then
b.tokens -= 1
return true
end
return false
end
return TokenBucket
Aby użyć modułu, najpierw tworzysz nową instancję limitera za pomocą TokenBucket.new(capacity, windowSeconds). capacity to maksymalna liczba żądań, jakie użytkownik może złożyć w szybkim wybuchu, a windowSeconds określa, jak długo trwa napełnienie wszystkich tych tokenów. Na przykład, TokenBucket.new(5, 10) tworzy limiter, który pozwala na wybuchy do 5 żądań i napełnia jeden token co dwie sekundy (10 sekund / 5 tokenów).
Przed wykonaniem chronionej logiki, wywołaj metodę :allow(userId), odmawiając działania, jeśli zwraca fałsz. Dobrym pomysłem jest również czyszczenie danych kubła użytkownika, gdy opuszczają grę, aby zapobiec wyciekom pamięci.
Poniższy przykładowy skrypt pokazuje, jak zabezpieczyć RemoteEvent używany do niestandardowego systemu czatu przed byciem spamowanym przez graczy.
-- Przykład użycia w skrypcie serwera
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local TokenBucket = require(ServerScriptService.TokenBucket)
local ChatRemote = ReplicatedStorage:WaitForChild("ChatRemote") :: RemoteEvent
-- 5 wiadomości na 10 sekund (pojemność 5, napełnia się przy 0.5 tokenów/sek)
local chatLimiter = TokenBucket.new(5, 10)
ChatRemote.OnServerEvent:Connect(function(player: Player, message: string)
if not chatLimiter:allow(player.UserId) then
-- Zbyt szybko: zignorować zapytanie lub wysłać ostrzeżenie o spamie
return
end
-- Przetwórz wiadomość (po innych walidacjach)
broadcastMessage(player, message)
end)
-- Czyszczenie, aby zapobiec wyciekom pamięci
Players.PlayerRemoving:Connect(function(p: Player)
chatLimiter.buckets[p.UserId] = nil
end)
Zabezpieczanie instancji wywoływanych przez klienta
ProximityPrompts, ClickDetectors i DragDetectors nie są zdalnymi wywołaniami, ale oszust może wywołać ich zdarzenia z dowolnej odległości, o każdej porze, często ignorując właściwości takie jak Enabled lub MaxActivationDistance. Te obiekty muszą być zabezpieczone z taką samą starannością jak zdalne wywołania.
ProximityPrompt
- Oszust może wywołać dowolne zdarzenie, nawet jeśli Enabled jest fałszywe na serwerze.
- Enabled, MaxActivationDistance i RequiresLineOfSight mogą być cicho zmienione po stronie klienta, więc każde replikowane ProximityPrompt może być widoczne i potencjalnie interaktywne z dowolnej lokalizacji.
- Serwer zaakceptuje wydarzenia hold (takie jak PromptButtonHoldBegan), nawet jeśli serwerowe HoldDuration to zero. Klient może manipulować HoldDuration, aby wykonać interakcje nienaturalnie szybko.
- Tylko zdarzenie Triggered ma sprawdzenie odległości po stronie serwera. Inne zdarzenia (jak PromptButtonHoldBegan i TriggerEnded) nie mają żadnego sprawdzenia odległości i mogą być wysyłane przez dowolnego klienta.
ClickDetector
- Na serwerze nie ma żadnych kontroli dla żadnych zdarzeń.
- Oszuści mogą replikować dowolne zdarzenie z dowolnej odległości, nawet jeśli ClickDetector jest wyłączony (MaxActivationDistance wynoszące 0 lub DragDetector.Enabled fałszywe).
- Zdarzenia mogą być replikowane, nawet jeśli ClickDetector nie jest rodzicem czegoś, co można kliknąć.
DragDetector
- Dla zdarzeń odziedziczonych po ClickDetector, nie ma żadnych kontroli.
- Dla zdarzeń przeciągania właściwości Enabled oraz PermissionPolicy są sprawdzane i respektowane przez serwer. Wszystkie inne właściwości nie są sprawdzane.
Gdy jedno z tych zdarzeń zostaje uruchomione, Twój skrypt serwera powinien przeprowadzić swoją własną walidację przed podjęciem jakiejkolwiek akcji:
- Sprawdź, czy jest włączone: Upewnij się, że instancja została zaprojektowana jako aktywna, sprawdzając jej właściwości (takie jak Enabled, HoldDuration, RunLocally itp.) po stronie serwera. Ten krok nie jest konieczny, jeśli obiekt jest zawsze włączony.
- Sprawdź stan gracza: Upewnij się, że gracz może wykonać daną akcję, biorąc pod uwagę aktualny stan gracza. Czy postać gracza jest w świecie? Czy ich postać jest wystarczająco blisko obiektu? Czy gracz musi być żywy, aby móc z interakcji z tym obiektem? Sprawdź wszelkie inne stany gracza, które mogą być utrzymywane przez Twoje doświadczenie po stronie serwera, w razie potrzeby.
- Zastosuj ograniczenie prędkości: Jak omówiono w Ograniczaniu prędkości, zastosuj cooldowny po stronie klienta i wymuś ograniczenia prędkości po stronie serwera, aby zapobiec spammingowi tych zdarzeń i ich nadużywaniu. Dla interakcji, które mają zająć minimalny czas, upewnij się, że klienci nie ukończą ich zbyt szybko.
Uwaga na temat własności sieciowej: Domyślnie, jeśli jakiekolwiek z tych obiektów są dziećmi nieprzytwierdzonych części lub nieprzytwierdzonego zespołu, oszust może przejąć własność sieciową rodzicielskich części i przenieść je bezpośrednio do swojej postaci, omijając kontrole odległości. W przypadku krytycznych akcji użyj przytwierdzonych części lub API własności sieciowej wraz z niezbędną walidacją po stronie serwera.
RemoteEvents i RemoteFunctions
Ogranicz zakres i wpływ RemoteFunctions i RemoteEvents. Zachowaj szczególną ostrożność w dynamicznym ładowaniu jakichkolwiek zasobów (nawet tekstur lub dźwięków) lub uruchamianiu kodu doświadczenia (szczególnie przez require) na podstawie argumentów funkcji/zdalnych wywołań. Unikaj wdrażania zdalnych wywołań, które pozwalają klientowi na określenie dowolnej ścieżki lub odniesienia do instancji, który serwer ma usunąć lub zmodyfikować, nawet jeśli takie modyfikacje wydają się trywialne. Zdalne wywołania, które mogą zmieniać dowolny stan instancji lub dokonywać szerokich zmian w drzewie DataModel, mogą być często łączone z innymi błędami, które poważnie wpływają na ogólny stan lub logikę. Sprawdź nie tylko typy argumentów instancji, ale także klasę oraz oczekiwaną lokalizację lub strukturę w obrębie DataModel.
Komunikacja klient-klient
Niektóre zdalne wywołania są zaprojektowane, aby umożliwić jednemu klientowi wywoływanie efektów na innych klientach. Dzieje się tak, gdy klient wysyła zdalne żądanie do serwera, który następnie używa RemoteEvent:FireAllClients(), RemoteEvent:FireClient() lub podobnych metod do przekazywania informacji. Ten wzorzec jest niebezpieczny, jeśli nie jest odpowiednio zabezpieczony - serwer musi być bramkarzem, a nie tylko przekaźnikiem.
Wyobraź sobie scenariusz, w którym gracze mogą rzucać zaklęcie błyskawicy. Gdy zaklęcie jest rzucane, pobliscy gracze widzą błysk, słyszą dźwięk i doświadczają wstrząsu kamery.
Podatny skrypt serwera może wyglądać tak:
Podatny kod serwera (skrypt w ServerScriptStorage)
local castLightningEvent = game.ReplicatedStorage.CastLightning
-- Ten skrypt serwera przekazuje wiadomość wszystkim bez żadnej walidacji
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
castLightningEvent:FireAllClients(strikePosition)
end)
Kod efektu klienta
local castLightningEvent = game.ReplicatedStorage.CastLightning
-- Tworzenie efektów wizualnych i dźwiękowych po stronie klienta
local function createLightningEffect(strikePosition)
-- Kod do wstrząsania kamerą
-- Kod do odtwarzania głośnego dźwięku błyskawicy
-- Kod do tworzenia wizualnej błyskawicy
print("Błyskawica uderza w: " .. tostring(strikePosition))
end
-- Gdy serwer nadaje zdarzenie, ten klient uruchamia efekt
castLightningEvent.OnClientEvent:Connect(createLightningEffect)
Oszust może wykorzystać ten podatny system na kilka sposobów:
- Spamowanie: Wywołując zdalne żądanie setki razy na sekundę, powodując nieprzerwaną akcję, która sprawia, że doświadczenie jest niegrywalne.
- Nieprawidłowe dane: Wysyłanie nil, NaN lub niepoprawnych typów, które powodują błędy skryptowe u wszystkich klientów.
- Nieautoryzowane użycie: Rzucanie zaklęć, które nie zostały odblokowane lub na które nie ma zasobów.
Serwer musi być bramkarzem, a nie przekaźnikiem. Przed nadaniem musi zweryfikować prośbę i zastosować ograniczenie prędkości na poziomie gracza. Oto przykładowe metody, które można zastosować do walidacji żądania.
Bezpieczny kod serwera
local castLightningEvent = game.ReplicatedStorage.CastLightning
local playerCooldowns = {}
local COOLDOWN_TIME = 3
castLightningEvent.OnServerEvent:Connect(function(player, strikePosition)
-- 1. Walidacja typu
if typeof(strikePosition) ~= "Vector3" then
return -- Niepoprawny typ, odrzuć milcząco
end
-- 2. Sprawdź NaN (NaN ~= NaN to jedyny sposób, aby to wykryć)
if strikePosition.X ~= strikePosition.X then
return -- Zawiera NaN, odrzuć
end
-- 3. Przykładowe ograniczenie prędkości
local lastCast = playerCooldowns[player] or 0
if tick() - lastCast < COOLDOWN_TIME then
return -- Nadal na cooldownie
end
-- 4. Przykładowa walidacja uprawnień
if not player:GetAttribute("HasLightningSpell") then
return -- Gracz nie ma tego zaklęcia
end
-- 5. Przykładowa walidacja zasięgu
local character = player.Character
local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then
return
end
local distance = (humanoidRootPart.Position - strikePosition).Magnitude
if distance > 100 then
return -- Poza zasięgiem
end
-- Wszystkie kontrole przeszły - można bezpiecznie nadawać
playerCooldowns[player] = tick()
castLightningEvent:FireAllClients(player, strikePosition)
end)
Wprowadzając te kontrole, staje się trudniej dla oszusta zrujnować doświadczenie innym graczom. Serwer działa jako bariera ochronna, zapewniając, że tylko ważne, autoryzowane działania są przekazywane innym klientom.
Szczególne rozważania
Poniżej znajdują się dodatkowe przypadki, które należy rozważyć i które mogą wymagać specjalistycznego podejścia.
Manipulacja magazynem danych
W doświadczeniach korzystających z DataStoreService do zapisywania danych graczy, oszuści mogą wykorzystać niepoprawne dane, warunki wyścigu do usunięcia zapisów lub zduplikowania przedmiotów w magazynie danych. To jest szczególnie problematyczne w doświadczeniach z wymianą przedmiotów, rynkami i systemami walutowymi.
Upewnij się, że wszystkie operacje wykonywane przez RemoteEvent lub RemoteFunction, które wpływają na dane gracza z danymi wejściowymi klienta, są sanowane na podstawie następujących kryteriów:
- Wartości instancji nie mogą być serializowane do magazynu danych i spowodują błąd. Wykorzystuj walidację typów, aby temu zapobiec.
- Magazyny danych mają ograniczenia danych. Ciągi o dowolnej długości powinny być sprawdzane i/lub ograniczone, aby uniknąć tego, zapewniając, że klucze o nieograniczonej długości nie mogą być dodawane do tabel przez klienta.
- Indeksy tabel nie mogą być NaN lub nil. Iteruj przez wszystkie tabele przekazane przez klienta i upewnij się, że wszystkie indeksy są ważne.
- Magazyny danych mogą akceptować tylko poprawne znaki UTF-8, dlatego powinieneś sanować wszystkie ciągi dostarczone przez klienta za pomocą utf8.len(), aby upewnić się, że są poprawne. utf8.len() zwróci długość ciągu, traktując znaki unicode jako jeden znak; jeśli napotka niepoprawny znak UTF-8, zwróci nil oraz pozycję niepoprawnego znaku. Należy zauważyć, że niepoprawne ciągi UTF-8 mogą również znajdować się w tabelach jako klucze i wartości.
- Eksploity związane z liczbami nieskończoności/NaN - Oszuści mogą wysyłać wartości takie jak -1/0, 0/0, lub math.huge, aby spowodować nieskończoną walutę lub przerwać obliczenia. Zawsze waliduj za pomocą metod opisanych w niebezpieczeństwie NaN.
Warunki wyścigu mogą wystąpić, gdy gracze manipulują czasem między operacjami, aby zduplikować przedmioty lub zepsuć dane.
Przykład eksploitu handlowego: Gracz inicjuje wymianę, wysyła swój przedmiot do innego gracza, a następnie natychmiast opuszcza grę. Jeśli wymiana się kończy, ale zapis w magazynie danych zawodzi z powodu niepoprawnych danych, ponownie dołącza z oryginalnymi przedmiotami, podczas gdy inny gracz zatrzymuje wymienione przedmioty - co prowadzi do duplikacji.
Strategie zapobiegania:
- Waliduj wszystkie dane przed jakimikolwiek operacjami wymiany.
- Użyj wzorców przypominających transakcje, gdzie wszystkie dane graczy są walidowane przed zatwierdzeniem jakichkolwiek zmian.
- Wprowadź odpowiednie zarządzanie błędami, które cofa wszystkie zmiany, jeśli którakolwiek część nie powiedzie się.
MarketplaceService
W przypadku interakcji z MarketplaceService, takich jak przepustki lub produkty dla dewelopera, cała walidacja zakupu i przyznawania przedmiotów musi odbywać się po stronie serwera. W szczególności, użyj zwrotnego połączenia ProcessReceipt, aby bezpiecznie walidować potwierdzenia zakupu. Nigdy nie ufaj sygnałowi po stronie klienta, takiemu jak PromptProductPurchaseFinished, aby potwierdzić zakup bez weryfikacji po stronie serwera, ponieważ mogą one być oszukane. Upewnij się, że Twoja funkcja ProcessReceipt dokładnie sprawdza szczegóły potwierdzenia i przyznaje przedmioty lub walutę tylko po potwierdzeniu legalnej transakcji z serwerami Roblox, oraz bądź przygotowany na obsługę przypadków, w których produkt został już przyznany dla danego potwierdzenia.