Tácticas de seguridad y mitigación de trampas

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

Roblox utiliza un sistema de física distribuida en el que los clientes tienen la custodia de la simulación física de objetos en su control, generalmente el personaje del jugador y objetos sin anclar cerca de ese personaje.Además, a través del uso de software de terceros, los explotadores pueden ejecutar código arbitrario de Luau en el cliente para manipular el modelo de datos del cliente y descompilar y ver el código que se ejecuta en él.

Colectivamente, esto significa que un explotador hábil puede potencialmente ejecutar código para engañar en tu juego, incluyendo:

  • Teletransportando su propio personaje alrededor del lugar.
  • Disparar sin seguridad RemoteEvents o invocar RemoteFunctions , como para otorgarles artículos sin ganarlos.
  • Ajustar el carácter de su personaje WalkSpeed para que se mueva muy rápido.

Si bien puedes implementar defensas de diseño limitadas para atrapar ataques comunes, se recomienda encarecidamente que implementes tácticas de mitigación más confiables del lado del servidor, ya que el servidor es la autoridad final para cualquier experiencia de ejecución.

Tácticas de diseño defensivo

Las decisiones de diseño básicas pueden servir como medidas de seguridad "primer paso" para disuadir a los exploits.Por ejemplo, en un juego de disparos en el que los jugadores obtienen puntos por matar a otros jugadores, un explotador puede crear un montón de bots que se teletransporten al mismo lugar para que puedan ser asesinados rápidamente por puntos.Dada esta posible explotación, considere dos enfoques y su resultado predecible:

AproximaciónResultado predecible
Persigue a los bots escribiendo código que intenta detectarlos.
Reducir o eliminar por completo los ganancios de puntos por muertes en jugadores recién generados.

Aunque el diseño defensivo obviamente no es una solución perfecta o completa, puede contribuir a un enfoque de seguridad más amplio, junto con mitigación del lado del servidor.

Mitigación del lado del servidor

En la medida de lo posible, el servidor debe emitir el veredicto final sobre lo que es "verdadero" y cuál es el estado actual del mundo.Los clientes pueden, por supuesto, solicitar al servidor que realice cambios o realice una acción, pero el servidor debe validar y aprobar cada uno de estos cambios/acciones antes de que los resultados se replican a otros jugadores.

Con la excepción de ciertas operaciones de física, los cambios al modelo de datos en el cliente no se replican al servidor, por lo que el principal camino de ataque es a menudo a través de los eventos de red que has declarado con RemoteEvents y RemoteFunctions .Recuerde que un explotador que ejecuta su propio código en su cliente puede invocarlos con cualquier dato que desee.

Validación del tipo de tiempo de ejecución remoto

Un camino de ataque es para que un explotador invoque RemoteEvents y RemoteFunctions con argumentos de introducirincorrecto.En algunos escenarios, esto puede causar que el código en el servidor escuche estas remotes de manera que sea ventajosa para el explotador.

Al utilizar eventos/funciones remotos, puedes evitar este tipo de ataque al validar los tipos de argumentos pasados en el servidor.El módulo "t" , disponible aquí, es útil para verificar el tipo de esta manera.Por ejemplo, asumiendo que el código del módulo existe como un ModuleScript llamado t dentro de ReplicatedStorage :

LocalScript en StarterPlayerScripts

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
-- Pasar el color y la posición de la parte al invocar la función
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
Script en ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
local t = require(ReplicatedStorage:WaitForChild("t"))
-- Cree un validador de tipo en avance para evitar sobrecargas innecesarias
local createPartTypeValidator = t.tuple(t.instanceIsA("Player"), t.Color3, t.Vector3)
-- Crear una nueva parte con las propiedades pasadas
local function createPart(player, partColor, partPosition)
-- Compruebe el tipo de los argumentos pasados
if not createPartTypeValidator(player, partColor, partPosition) then
-- Devuelve silenciosamente "false" si la comprobación de tipo falla aquí
-- Levantar un error sin tiempo de reutilización se puede abusar para ralentizar el servidor
-- ¡Proporciona comentarios del cliente en su lugar!
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
-- Vincular "createPart()" a la llamada de devolución de la función remota
remoteFunction.OnServerInvoke = createPart

Validación de datos

Otro ataque que los explotadores podrían lanzar es enviar tipos técnicamente válidos pero hacerlos extremadamente grandes, largos o de otra forma defectuosos.Por ejemplo, si el servidor tiene que realizar una operación costosa en una cadena que se escala con la longitud, un explotador podría enviar una cadena increíblemente grande o malformada para obstruir el servidor.

Del mismo modo, tanto inf como NaN causarán type() como number , pero ambos pueden causar problemas importantes si un explotador los envía y no se manejan correctamente a través de funciones como las siguiendo:


local function isNaN(n: number): boolean
-- NaN nunca es igual a sí mismo
return n ~= n
end
local function isInf(n: number): boolean
-- El número podría ser -inf o inf
return math.abs(n) == math.huge
end

Otro ataque común que los explotadores pueden usar implica enviar tables en lugar de un Instance.Los cargamentos complejos pueden imitar lo que sería una referencia de objeto ordinaria de otra manera.

Por ejemplo, provisto con un sistema de tienda en experiencia donde los datos de los artículos como precios se almacenan en objetos , un explotador puede evadir todas las otras comprobaciones haciendo lo siguiendo:

LocalScript en 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, -- También se podrían usar valores negativos, ¡lo que resultaría en dar moneda en lugar de tomarla!
},
}
-- Enviar carga útil maliciosa al servidor (esto será rechazado)
print(buyItemEvent:InvokeServer(payload)) -- Genera "false artículo no válido proporcionado"
-- Envíe un artículo real al servidor (¡esto pasará!)
print(buyItemEvent:InvokeServer(itemDatafolder["Real Blade"])) -- Outputs "true" and remaining currency if purchase succeeds
Script en ServerScriptService

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- Compruebe si el artículo pasado no se ha falseado y está en la carpeta ItemData
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- El servidor puede continuar para procesar la compra basada en el flujo de ejemplo a continuación
end
-- Vincular "buyItem()" a la llamada de devolución de la función remota
buyItemEvent.OnServerInvoke = buyItem

Validación de valor

Además de validar tipos y datos , debe validar los valores transmitidos a través de RemoteEvents y RemoteFunctions , asegurándose de que sean válidos y lógicos en el contexto solicitado.Dos ejemplos comunes son una tienda en experiencia y un sistema de objetivo de armas .

compraren experiencia

Considera un sistema de tienda en experiencia con una interfaz de usuario, por ejemplo, un menú de selección de productos con un botón de "Comprar".Cuando se presiona el botón, puedes invocar un RemoteFunction entre el cliente y el servidor para solicitar la compra.Sin embargo, es importante que el servidor , el gerente más confiable de la experiencia, confirme que el usuario tiene suficiente dinero para comprar el objeto.

Example purchase flow from client to server through a RemoteEvent
Ejemplo de flujo de compra del cliente al servidor a través de un RemoteFunction

Objetivo de armas

Los escenarios de combate requieren una atención especial para validar los valores, particularmente a través de la validación de objetivos y golpes.

Imagina un juego donde un jugador pueda disparar un rayo láser a otro jugador.En lugar de que el cliente le diga al servidor a quién dañar, debería decirle al servidor la posición de origen del disparo y la parte/posición que cree que ha golpeado.El servidor puede luego validar lo siguiendo:

  • La posición que el cliente informa de disparo desde está cerca del personaje del jugador en el servidor.Tenga en cuenta que el servidor y el cliente se diferenciarán ligeramente debido a la latencia, por lo que se deberá aplicar una tolerancia adicional.

  • La posición que el cliente informa golpeando está razonablemente cerca de la posición de la parte que el cliente informa que golpea, en el servidor.

  • No hay obstrucciones estáticas entre la posición desde la que el cliente informa de disparo y la posición a la que el cliente informa de disparo.Esta comprobación garantiza que un cliente no está tratando de disparar a través de las paredes.Tenga en cuenta que esto solo debe verificar la geometría estática para evitar que se rechacen los disparos válidos debido a la latencia. Además , es posible que desees implementar validaciones adicionales del lado del servidor como sigue:

  • Rastrea cuándo el jugador disparó por última vez su arma y valida para asegurarte de que no está disparando demasiado rápido.

  • Rastrea la cantidad de munición de cada jugador en el servidor y confirma que un jugador disparador tiene suficiente munición para ejecutar el ataque de armas.

  • Si has implementado equipos o un sistema de combate "jugadores contra bots", confirma que el personaje golpeado es un enemigo, no un compañero de equipo.

  • Confirma que el jugador golpeado está vivo.

  • Almacene el estado de arma y jugador en el servidor y confirme que un jugador de disparo no está bloqueado por una acción actual como recargar o un estado como correr.

Manipulación del almacén de datos

En las experiencias que utilizan DataStoreService para guardar los datos del jugador, los explotadores pueden aprovechar los datos inválidos y métodos más oscuros, para evitar que un DataStore guarde correctamente.Esto se puede abusar especialmente en experiencias con el comercio de artículos, mercados y sistemas similares en los que los artículos o la moneda salen del inventario de un jugador.

Asegúrese de que cualquier acción realizada a través de un RemoteEvent o RemoteFunction que afecte los datos del jugador con la entrada del cliente se limpie según lo siguiendo:

  • Instance los valores no se pueden serializar en un DataStore y fallarán. Utilice validación de tipo para evitar esto.
  • DataStores tienen límites de datos .Se deben verificar y/o limitar las cadenas de cualquier longitud para evitar esto, junto con garantizar que las llaves arbitrarias ilimitadas no se puedan agregar a las tablas por el cliente.
  • Los índices de tabla no pueden ser NaN o nil. Iterar sobre todas las tablas pasadas por el cliente y verificar que todos los índices sean válidos.
  • DataStores solo puede aceptar caracteres válidos de UTF-8, por lo que deberías sanear todas las cadenas proporcionadas por el cliente a través de utf8.len() para asegurarte de que sean válidas. utf8.len() devolverá la longitud de una cadena, tratando los caracteres de idioma como un solo carácter; si se encuentra un carácter UTF-8 inválido, devolverá nil y la posición del carácter inválido.Tenga en cuenta que las cadenas inválidas de UTF-8 también pueden estar presentes en las tablas como claves y valores.

Aceleración remota

Si un cliente es capaz de hacer que tu servidor complete una operación costosa en términos de tiempo o acceda a un servicio de tasa limitada como DataStoreService a través de un RemoteEvent, es crítico que implementes límite de velocidad para garantizar que la operación no se llame con demasiada frecuencia.La limitación de la tasa se puede implementar rastreando cuándo el cliente invocó por última vez un evento remoto y rechazando la siguiente solicitud si se llama demasiado pronto.

Validación de movimiento

Para experiencias competitivas, es posible que desees validar los movimientos del personaje del jugador en el servidor para asegurarte de que no se teletransportan alrededor del mapa o se mueven más rápido de lo que es aceptable.

  1. En incrementos de 1 segundo, compruebe la nueva ubicación del personaje contra una ubicación previamente almacenada.

    Image showing moving character's position on a straight path in increments of 1 second
  2. Determine un cambio máximo "tolerable" en la distancia basado en los WalkSpeed (studs por segundo) del personaje, multiplicado por ~1.4 para permitir cierta indulgencia contra la latencia del servidor.Por ejemplo, en un valor predeterminado de WalkSpeed, un delta tolerable es ~22.

    Image showing tolerable change in distance based on character's walk speed
  3. Compare la diferencia de distancia actual contra la diferencia tolerable y proceda como sigue:

    • Para un delta tolerable, almacene la nueva ubicación del personaje en preparación para la próxima verificar, comprobarincrementada.
    • Para un delta inesperado o intolerable (código malicioso, vulnerabilidad del sistemade velocidad/teletransporte potencial):
      1. Incrementar un valor separado de "número de ofensas" para el jugador, en lugar de penalizarlo por un "positivo falso" resultante de la latencia extrema del servidor o de otros factores no explotables.
      2. Si ocurren un gran número de ofensas en un período de 30-60 segundos, Kick() el jugador de la experiencia por completo; de lo contrario, restablece el recuento de "número de ofensas".Tenga en cuenta que cuando expulsa a un jugador por fraude, es mejor practicar grabar el evento para que pueda realizar un seguimiento de cuántos jugadores se ven afectados.