保安策略和欺詐防禦

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

Roblox 使用一個 分佈式物理系統 ,其中客戶對控制中的物體的物理模擬有監管權,通常是玩家的角色和與角色附近的未錨定物體。此外,通過使用第三方軟件,惡意程式可以在客戶端上執行任意 Luau 代碼來操縱其客戶端的數據模型並反編譯和查看在其上運行的代碼。

總的來說,這意味著熟練的濫用者可能會執行代碼來欺騙您的遊戲,包括:

  • 傳送自己的角色到空間地周圍。
  • 發射未鞏固的 RemoteEvents 或呼叫 RemoteFunctions ,例如授予自己物品而不賺取它們。
  • 調整角色的 WalkSpeed 以便它移動得很快。

雖然您可以實裝有限的 設計防禦 來捕捉常見攻擊,但建議您實裝更可靠的 服務器側緩解策略,因為服務器是任何運行體驗的最終權威。

防禦性設計策略

基本設計決定可以作為「第一步」安全措施來減少漏洞。例如,在一個射擊遊戲中,玩家通過殺死其他玩家獲得分數,漏洞可以創建一群機器人,這些機器人會傳送到同一地點,因此可以快速被殺死以獲得分數。考慮到這種潛在入侵套件,請考慮兩種方法和它們預測的結果:

方法可預測的結果
寫代碼來尋找機器人,以嘗試偵測它們。
減少或直接移除新生玩家擊殺時獲得的點數。

雖然防禦設計當然不是完美或完整的解決方案,但它可以與 服務器端緩解 一起,為更廣泛的安全方法做出貢獻。

服務器側減緩

盡可能地, 伺服器 應該對什麼是「真實」以及世界當前狀態做最後判斷。客戶可以要求伺服器進行變更或執行行動作,但伺服器應該 驗證並批准 這些變更/行動之前將結果複製給其他玩家。

除了某些物理操作外,客戶端上的數據模型變更不會複製到服務伺服器,因此主攻擊路徑通常是透過您使用 RemoteEventsRemoteFunctions 宣言的網路事件來實現的。請記住,在您的客戶端上執行自己代碼的漏洞程式可以使用任何他們想要的資料來呼叫這些。

遠端運行時類型驗證

一個攻擊路徑是讓漏洞利用者呼叫 RemoteEventsRemoteFunctions 並使用錯誤類輸入的參數。在某些情況下,這可能會導致服務器上的代碼聆聽這些遠端的方式發生錯誤,這對惡意攻擊者有利。

當使用遠端事件/功能時,您可以通過在伺服器上驗證 類型 傳送參數來防止這種類型的攻擊。模組 "t" , 可在此處使用 , 進行類型檢查有用。例如,假設模組的代碼存在於 ModuleScript 命名為 tReplicatedStorage 內:

啟動器玩家腳本中的本地腳本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
-- 在呼叫函數時傳遞部分顏色和位置
local newPart = remoteFunction:InvokeServer(Color3.fromRGB(200, 0, 50), Vector3.new(0, 25, 0))
if newPart then
print("The server created the requested part:", newPart)
elseif newPart == false then
print("The server denied the request. No part was created.")
end
服務器腳本服務中的腳本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
local t = require(ReplicatedStorage:WaitForChild("t"))
-- 事先創建類型驗證器以避免不必要的開銷
local createPartTypeValidator = t.tuple(t.instanceIsA("Player"), t.Color3, t.Vector3)
-- 使用傳遞的屬性創建新零件
local function createPart(player, partColor, partPosition)
-- 類型檢查通過的參數
if not createPartTypeValidator(player, partColor, partPosition) then
-- 在此處檢查類型失敗時悄悄返回「false」
-- 在冷卻期無法提升錯誤可能會被濫用來拖慢伺服器
-- 提供客戶反饋代理!
return false
end
print(player.Name .. " requested a new part")
local newPart = Instance.new("Part")
newPart.Color = partColor
newPart.Position = partPosition
newPart.Parent = Workspace
return newPart
end
-- 將「createPart()」綁定到遠端功能的回調
remoteFunction.OnServerInvoke = createPart

數據驗證

惡意攻擊者可能會發起的另一個攻擊是發送 技術上有效的類型 但使它們極其巨大、長或其他不正確的形式。例如,如果伺服器必須對增長速度的字串進行昂貴的操作,惡意程式碼可以向伺服器發送極其巨大或無效的字串,使伺服器停機。

相同地,兩個 infNaN 都會 type() 作為 number ,但兩個都可能導致嚴重問題,如果惡意程式發送它們,並且它們沒有通過像以追蹤中功能一樣正確處理:


local function isNaN(n: number): boolean
-- NaN 永遠不會等於自己
return n ~= n
end
local function isInf(n: number): boolean
-- 數字可以是 -inf 或 inf
return math.abs(n) == math.huge
end

惡意程式碼使用者可能使用的另一個常見攻擊是將 tables 替換為 Instance 。複雜的載入物可以模擬一般對象參考的形式。

例如,提供了 體驗內商店 系統,其中物品數據,如價格,存儲在 NumberValue 對象中,惡意程序可以通過以追蹤中方式繞過所有其他檢查:

啟動器玩家腳本中的本地腳本

local 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, -- 也可以使用負值,結果是給予貨幣而不是取走它!
},
}
-- 向伺服器發送惡意載件 (這將被拒絕)
print(buyItemEvent:InvokeServer(payload)) -- 輸出「提供了無效的項目」
-- 向服務器發送實際物品(這將通過!)
print(buyItemEvent:InvokeServer(itemDatafolder["Real Blade"])) -- Outputs "true" and remaining currency if purchase succeeds
服務器腳本服務中的腳本

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- 檢查傳送的項目是否未被冒犯,並存在在物品資料夾中
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- 伺服器可以繼續處理以下示例流程的購買
end
-- 將「buyItem()」綁定到遠端功能的回調
buyItemEvent.OnServerInvoke = buyItem

值驗證

除了驗證 類型資料 之外,您還應該驗證 通過 RemoteEventsRemoteFunctions 傳送,確保它們在要求的上下文中是有效且邏輯的。兩個常見例子是經驗內的商店和武器瞄準系統。

經驗內購物

考慮經驗內商店系統,例如具有「購買」按鈕的產品選擇菜單。當按鈕被按下時,您可以在客戶端和伺服器之間呼叫 RemoteFunction 來請求購買。然而,最重要的是, 伺服器 ,體驗最可靠的管理員,必須確認使用者有足夠的金錢來購買物道具。

Example purchase flow from client to server through a RemoteEvent
從客戶端到服務器的例子購買流程通過 RemoteFunction

武器瞄準

戰鬥場景需要特別注意驗證值,特別是通過瞄準和命中驗證。

想像一個遊戲,玩家可以向另一名玩家發射雷射光束。而不是讓客戶告知伺服器 要受到傷害,應該告知伺服器射擊的起源位置和它認為已經擊中的零件/位置。伺服器可以驗證以追蹤中內容:

  • 客戶端報告的 射擊位置 靠近服務器上玩家角色。請注意,伺服器和客戶端由於延遲會略有不同,因此額外的寬容度需要應用。

  • 客戶報告的位置 擊中 與伺服器務器上客戶報告擊中的 部分 位置相對較近。

  • 沒有從客戶端報告射擊的位置到客戶端報告射擊的位置之間的靜態障礙。這項檢查確保客戶端不試圖穿過牆壁射擊。請注意,這只應檢查靜態幾何圖形,以避免由於延遲而拒絕有效射擊。 額外 ,您可能想要實現以下服務器側驗證:

  • 追蹤玩家上次使用武器射擊時間,並進行驗證以確保他們沒有過快射擊。

  • 在伺服器上追蹤每個玩家的彈藥數量,確認發射玩家擁有足夠的彈藥來執行武器攻擊。

  • 如果您已實裝 隊伍 或「玩家對抗機器人」戰鬥系統,請確認命中角色是敵人,不是隊友。

  • 確認命中的玩家是否存活。

  • 在伺服器上儲存武器和玩家狀態,確認發射玩家不被現有操作,例如重新載入或衝刺狀態阻止。

數據存儲操作

在使用 DataStoreService 儲存玩家資料的體驗中,惡意程式可利用無效的 資料 和更加神秘的方法,防止 DataStore 正確儲存。這可能在與物品交易、市場和類似系統相關的體驗中特別濫用,其中物品或貨幣會離開玩家的道具欄。

確保任何通過 RemoteEventRemoteFunction 對玩家數據進行影響的操作,都依照以追蹤中規定進行過濾:

  • Instance 值無法轉換為 DataStore 並會失敗。使用 類型驗證 來防止這種情況。
  • DataStores資料限制。應檢查和/或限制隨機長度的字串,以避免這種情況,同時確保客戶端無法添加無限隨機鍵到表。
  • 表索引不能是 NaNnil 。對所有由客戶傳送的表進行循環並確認所有索引都有效。
  • DataStores 只能接受有效的UTF-8字符,因此您應該通過 utf8.len() 將客戶提供的所有字串都去污染,以確保它們有效。utf8.len()將返回字串長度,將Unicode字元視為單一字元;如果遇到無效的UTF-8字元,將返回nil和無效字元的位置。請注意,無效的 UTF-8 字串也可以出現在表中作為鑰匙和值。

遠端限速

如果客戶能夠讓您的伺服器完成計算成本高的操作或存取限速服務,例如 DataStoreService 通過 RemoteEvent 來存取率限制服務,那麼您必須實施 限速率 來確保操作不會過於頻繁地被呼叫。限速可以通過跟蹤客戶最後一次呼叫遠端事件的時間並拒絕下一個請求來實現。如果過早呼叫,則下一個請求將被拒絕。

運動驗證

對於競爭體驗,您可能希望在服務器上驗證玩家角色的移動,以確保它們不會在地圖上傳送或移動得更快於接受的水平。

  1. 在 1 秒的增量中,檢查角色的新位置與之前已儲存的位置進行比較。

    Image showing moving character's position on a straight path in increments of 1 second
  2. 根據角色的 WalkSpeed (每秒點數) 來確定最大「可容納」變更的距離,乘以 ~1.4 以允許對服務器延遲的一些寬容度。例如,在預設值 WalkSpeed 的 16 上,可接受的差值是 ~22。

    Image showing tolerable change in distance based on character's walk speed
  3. 將實際距離差與容差進行比較,然後按照以下步驟繼續:

    • 對於可容忍的差異,在下一次增量檢查之前將角色的新位置存入緩存中。
    • 對於意外或無法忍受的差異 (潛在速度/傳送入侵套件):
      1. 對玩家增加一個獨立的「犯罪數量」值,而不是因為極端伺服器延遲或其他非惡意因素導致的「假正面」而懲罰他們。
      2. 如果在 30-60 秒期間發生大量違規,Kick() 從體驗中完全刪除玩家;否則,重置「違規數量」計數。注意,當踢除玩家作弊時,記錄事件是最好的做法,這樣您就可以跟蹤受影響的玩家數量。