安全技术和诱饵防御

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

Roblox 使用 分布式物理系统 ,在客户端对控制中的物理模拟对象有管辖,通常是玩家角色和未锚定对象附近的对象。此外,通过使用第三方软件的使用,黑客可以在客户端上运行任意 Lua 代码来操作其数据模型并从上面的代码进行逆向编译并查看运行在上面的代码。

共同地,这意味着有技术高超的漏洞者可能会在您的游戏中作弊,包括:

  • 将他们自己的角色传送到这个场景。
  • 发射未获得的RemoteEvents或调用RemoteFunctions,例如为自己赢得物品而不赚取它们。
  • 调整其角色的 WalkSpeed 以便它移动很快。

虽然您可以实现限制的设计防御来捕捉常见攻击,但高度建议您实现更可靠的服务器端 mitigation tactics,因为服务器是任何运行体验的最终权威。

防御性设计战术

基本设计决策可以作为“第一步”安全措施来服务“遏止漏洞”。 例如,在射击游戏中,玩家根据击杀其他玩家获得积分,可能会创建一堆机器人,它们将传送到同一个位置,以便快速杀死它们获得积分。 考虑到这种潜在的漏洞,请考虑两种方法和其预测结果:

接近可预测结果
通过写代码来追踪机器人。
减少或完全移除新生成玩家上的点击收益。

虽然防御性设计明显不是完美或包括性的解决方案,但它可以贡献更广泛的安全方法,并且与 服务器端防御性措施 一起使用。

服务器端隔离

尽可能地,服务器应该对世界上的事实和现状做出最终判断。客户可以,当然,请求服务器做出更改或执行操动作,但服务器必须先验证并批准这些变更/操作,然后才能将其复制到其他玩家。

除了某些物理操作外,客户端对数据模型的更改不会复制到服务器,因此主要攻击路径通常是通过您声称的 RemoteEventsRemoteFunctions 来网络事件。记住,在您的客户端运行自己的代码时,可以通过执行这些事件来调用它们。

远程运行时类型验证

一个攻击路径是让恶意的攻击者使用 RemoteEventsRemoteFunctions 使用错误输入的参数来启动。在某些场景中,这可能会导致服务器接收到这些遥控器的代码发生错误,从而对恶意的攻击者有利。

使用远程事件/函数时,您可以通过验证服务器上传传递的参数类型来防止此类型的攻击。模块 “t”,可用 在此 ,是类型检查的好助手。例如,假设模块的代码存在为 Class.ModuleScript 命名的

在 StarterPlayerScripts 中的本地脚本

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 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
-- 将“创建零件()”绑定到远程函数的回调
remoteFunction.OnServerInvoke = createPart

数据验证

黑客可能会发起的另一个攻击是发送 技术有效的类型 ,但使它们变得非常大、长或其他方式有形。例如,如果服务器必须在长度方面执行昂贵的操作,黑客可以发送一个巨大、非常长或其他方式有形的字符串来灭难服务器。

相同地, both infNaNtype() 作为 1> number1> ,但 both 可能会导致重大问题,如果有人发送它们并且它们不正确处理通过函数如关注中/正在关注:


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

另一个常见的攻击,可能使用的黑客包括发送 tablesInstance 的位置。复杂的付加载可以模拟其他寻常的对象引用。

例如,提供了一个 在体验商店 系统,在 NumberValue 对象中存储物品数据,一名恶寒程序员可以通过执行以关注中/正在关注操作来绕过所有其他检查:

在 StarterPlayerScripts 中的本地脚本

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)) -- 输出“ false 提供的物品无效”
-- 发送真实的物品到服务器 (这将通过!)
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

价值验证

除了验证 类型数据 之外,您还应该验证通过 Class.RemoteEvent|RemoteEvents 和 1> Class.RemoteFunction|RemoteFunctions1> 的值,确保它们在要求的上下文中是有效的和有道理的。 两个常见例子是一个

体验商店

考虑一个带有用户界面的在体验商店系统,例如一个“购买”按钮的产品选择菜单。当按钮被按下时,您可以在客户端和服务器之间调用一个 RemoteFunction 来请求购买。然而,您需要确保服务器端,体验管理器的最可靠管理员,确认用户有足够的钱购买物品。

Example purchase flow from client to server through a RemoteEvent
客户端到服务器通过一个RemoteFunction购买示例

武器瞄准

战斗场景中,要求特别注意验证价值,例如通过瞄准和命中验证。

想象一个游戏,玩家可以向另一个玩家发射激光。 而不是客户端告诉服务器 要伤害,它应该告诉服务器射击的原始位置和部位/位置,它认为它击中了。 服务器可以然后验证以关注中/正在关注内容:

  • 客户端报告的位置 射击从 位于服务器上的玩家角色附近。注意服务器和客户端由于延迟会有所不同,因此需要适用额外的容量。

  • 客户端报告的位置 击中 是理想靠近服务器报告击中的位置。

  • 客户端报告射击的位置和客户端报告射击的位置之间没有静电干扰。这种检查可以确保客户端不试图射击穿墙。注意,这只会检查静电几何,以避免因延迟而拒绝有效的射击。 额外 ,您可能需要实现以下服务器端验证:

  • 跟踪玩家上次使用武器的时间,并验证,以确保他们不会射击太快。

  • 在服务器上跟踪每个玩家的弹药数量,并确认发射玩家有足够的弹药执行武器攻击。

  • 如果您实现了团队或一个“玩家对抗机器人”战斗系统,请确认打击角色是敌人,不是队友。

  • 确认命中玩家还活着。

  • 在服务器上存储武器和玩家状态,并确认受到的射击玩家不是阻塞当前操作(例如重新加载或状态如冲刺)。

数据商店操作

在使用 DataStoreService 来保存玩家数据的体验中,黑客可以利用无效的数据,以及更隐秘的方法来防止DataStore 正常保存。这可以特别在与物品交易、市场places等类似系统的体验中使用。

确保通过 RemoteEventRemoteFunction 影响玩家数据与客户端输入的任何操作都基于以关注中/正在关注原则进行消毒:

  • Instance 值不能被序列化为 DataStore 并会失败。使用 类型验证 来防止此。
  • DataStores数据上限。 必须检查并/或限制任意长度的字符串以避免这种情况,并且确保客户端不能向表添加任意长度的任意键。
  • 表索引不能为 NaNnil 。遍历所有通过客户端传送的表,并验证所有索引是否有效。
  • DataStores 只能接受有效的 UTF-8 字符,因此您应该通过 utf8.len() 确保所有由客户端提供的字符都是有效的。

远程控制器

如果客户端能够让您的服务器完成一个高计算成本的操作,或者通过 DataStoreService 访问限制的访问,那么您必须实现RemoteEvent来确保操作不会被调用得太频繁。率限制可以通过跟踪客户端上次使用远程事件的时间并拒绝下一个请求来实现。率限制可以

移动验证

对于竞争体验,您可能需要验证服务器上的玩家角色移动在地图上移动或移动速度不足以确保他们不会传送到地图上或移动速度不足。

  1. 在 1 秒的时间增量中,检查角色的新位置与之前的位置是否相同。

    Image showing moving character's position on a straight path in increments of 1 second
  2. 根据角色的 WalkSpeed (每秒钟 studs),乘以 ~1.4 允许有一些额外的忍容对服务器的延迟。例如,在默认值 WalkSpeed 的 16 的情况下,一个可接受的 Delta 是 ~22。

    Image showing tolerable change in distance based on character's walk speed
  3. 将实际距离 Delta 与可接受 Delta 进行比较,并按照以下步骤进行:

    • 对于可以容忍的 Delta,请在下个增量检查之前将角色的新位置存储在内存中。
    • 对于无法预知或不可能的 Delta (潜在速度/传送漏洞):
      1. 为玩家增加一个单独的“罪魁数”值,而不是因滞后极端服务器或其他非爆炸因素而对其进行惩罚。
      2. 如果在 30-60 秒内发生了大量违规,Kick() 玩家从体验完全; 否则,重置“违规”计数。 注意,当击倒玩家作弊时,最佳实践是记录事件,以便您可以跟踪影响的玩家。