O Roblox usa um sistema de física distribuída em que os clientes têm guarda sobre a simulação física de objetos em seu controle, geralmente o personagem do jogador e objetos não ancorados perto desse personagem.Além disso, através do uso de software de terceiros, os exploiters podem executar código arbitrário do Luau no cliente para manipular o modelo de dados do cliente e descompilar e visualizar o código em execução nele.
Coletivamente, isso significa que um explorador habilidoso pode potencialmente executar código para trapacear em seu jogo, incluindo:
- Teletransportando seu próprio personagem ao redor do local.
- Atirar em não seguro RemoteEvents ou invocar RemoteFunctions, como para conceder itens a si mesmo sem ganhá-los.
- Ajustando o personagem deles WalkSpeed para que ele se mova muito rápido.
Embora você possa implementar defesas de design limitadas para capturar ataques comuns, é altamente recomendável que você implemente táticas de mitigação mais confiáveis do lado do servidor, pois o servidor é a autoridade final para qualquer experiência executada.
Táticas de design defensivo
Decisões de design básicas podem servir como medidas de segurança "primeiro passo" para desencorajar exploits.Por exemplo, em um jogo de tiro onde os jogadores recebem pontos por matar outros jogadores, um explorador pode criar um grupo de bots que se teletransportem para o mesmo lugar para que possam ser rapidamente mortos por pontos.Dado esse vulnerabilidadepotencial, considere duas abordagens e seu resultado previsível:
Aproximação | Resultado previsível |
---|---|
Perseguir bots escrevendo código que tenta detectá-los. | |
Reduza ou remova completamente os ganhos de pontos por mortes em jogadores recém-gerados. |
Embora o design defensivo obviamente não seja uma solução perfeita ou abrangente, ele pode contribuir para uma abordagem mais ampla de segurança, juntamente com mitigação do lado do servidor.
Medição do lado do servidor
Na medida do possível, o servidor deve emitir o veredicto final sobre o que é "verdadeiro" e qual é o estado atual do mundo.Os clientes podem, claro, solicitar ao servidor que faça alterações ou execute uma ação, mas o servidor deve validar e aprovar cada uma dessas alterações/ações antes de as resultados serem replicados para outros jogadores.
Com exceção de algumas operações de física, as alterações no modelo de dados no cliente não se replicam para o servidor, então o principal caminho de ataque é frequentemente através dos eventos de rede que você declarou com RemoteEvents e RemoteFunctions.Lembre-se que um explorador executando seu próprio código em seu cliente pode invocá-los com qualquer dado que desejar.
Validação do tipo de tempo de execução remoto
Um caminho de ataque é para um invasor invocar RemoteEvents e RemoteFunctions com argumentos do digitarincorreto.Em alguns cenários, isso pode causar código no servidor que ouve esses remotos a erro de uma maneira que seja vantajosa para o explorador.
Ao usar eventos/funções remotas, você pode impedir esse tipo de ataque validando os tipos de argumentos passados no servidor.O módulo "t" , disponível aqui, é útil para verificação de tipo dessa maneira.Por exemplo, assumindo que o código do módulo exista como um ModuleScript chamado t dentro de ReplicatedStorage :
LocalScript em StarterPlayerScripts
local ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")-- Passe a cor e a posição da parte ao invocar a funçãolocal newPart = remoteFunction:InvokeServer(Color3.fromRGB(200, 0, 50), Vector3.new(0, 25, 0))if newPart thenprint("The server created the requested part:", newPart)elseif newPart == false thenprint("The server denied the request. No part was created.")end
Script em ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
local t = require(ReplicatedStorage:WaitForChild("t"))
-- Crie um validador de tipo em antecipação para evitar sobrecarga desnecessária
local createPartTypeValidator = t.tuple(t.instanceIsA("Player"), t.Color3, t.Vector3)
-- Criar nova peça com as propriedades passadas
local function createPart(player, partColor, partPosition)
-- Verifique os argumentos passados
if not createPartTypeValidator(player, partColor, partPosition) then
-- Silenciosamente retorne "false" se a verificação de tipo falhar aqui
-- Levantar um erro sem um tempo de espera pode ser abusado para sobrecarregar o servidor
-- Forneça feedback do cliente em vez disso!
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
-- Vincule "createPart()" ao retorno da função remota
remoteFunction.OnServerInvoke = createPart
Validação de dados
Outro ataque que os exploradores podem lançar é enviar tipos técnicamente válidos mas torná-los extremamente grandes, longos ou de outra forma malformados.Por exemplo, se o servidor precisar realizar uma operação cara em uma string que escala com comprimento, um explorador poderia enviar uma corda incrivelmente grande ou mal formatada para sobrecarregar o servidor.
Da mesma forma, tanto inf e NaN vai type() como number , mas ambos podem causar problemas importantes se um explorador os enviar e eles não forem tratados corretamente através de funções como a seguindo:
local function isNaN(n: number): boolean
-- NaN nunca é igual a si mesmo
return n ~= n
end
local function isInf(n: number): boolean
-- O número poderia ser -inf ou inf
return math.abs(n) == math.huge
end
Outro ataque comum que os exploradores podem usar envolve o envio de tables em vez de um Instance.Cargas complexas podem imitar o que seria uma referência de objeto ordinária, de outra forma.
Por exemplo, fornecido com um sistema de loja na experiência onde os dados de itens, como preços, são armazenados em objetos NumberValue, um explorador pode contornar todas as outras verificações fazendo o seguindo:
LocalScript em 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, -- Valores negativos também podem ser usados, resultando em dar moeda em vez de tomá-la!},}-- Envie um payload malicioso para o servidor (isso será rejeitado)print(buyItemEvent:InvokeServer(payload)) -- Produz "false Item inválido fornecido"-- Envie um item real para o servidor (isso passará!)print(buyItemEvent:InvokeServer(itemDatafolder["Real Blade"])) -- Outputs "true" and remaining currency if purchase succeeds
Script em ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemDataFolder = ReplicatedStorage:WaitForChild("ItemData")
local buyItemEvent = ReplicatedStorage:WaitForChild("BuyItemEvent")
local function buyItem(player, item)
-- Verifique se o item passado não foi falsificado e está no arquivo ItemData
if typeof(item) ~= "Instance" or not item:IsDescendantOf(itemDataFolder) then
return false, "Invalid item provided"
end
-- O servidor pode então continuar processando a compra com base no fluxo de exemplo abaixo
end
-- Vincule "buyItem()" ao retorno da função remota
buyItemEvent.OnServerInvoke = buyItem
Validação de valor
Além de validar tipos e dados , você deve validar os valores transmitidos através de RemoteEvents e RemoteFunctions , garantindo que sejam válidos e lógicos no contexto solicitado.Dois exemplos comuns são uma loja na experiência e um sistema de alvo de armas.
Loja comprarexperiência
Considere um sistema de loja na experiência com uma interface de usuário, por exemplo, um menu de seleção de produtos com um botão "Comprar".Quando o botão é pressionado, você pode invocar um RemoteFunction entre o cliente e o servidor para solicitar a compra.No entanto, é importante que o servidor , o gerente mais confiável da experiência, confirme que o usuário tem dinheiro suficiente para comprar o item.

Alvo de arma
Cenários de combate exigem atenção especial na validação de valores, especialmente através da mira e validação de hit.
Imagine um jogo onde um jogador possa disparar um raio laser para outro jogador.Em vez do cliente dizer ao servidor quem para danificar, ele deve, em vez disso, dizer ao servidor a posição de origem do tiro e a parte/posição que ele acha que atingiu.O servidor pode então validar o seguindo:
A posição que o cliente relata atirando de está perto do personagem do jogador no servidor.Observe que o servidor e o cliente diferirão ligeiramente devido à latência, então será necessária uma tolerância extra.
A posição que o cliente relata atingindo está razoavelmente próxima da posição da parte que o cliente relata atingindo, no servidor.
Não há obstruções estáticas entre a posição de onde o cliente relata atirar e a posição para a qual o cliente relata atirar.Essa verificação garante que um cliente não esteja tentando atirar através das paredes.Observe que isso deve verificar apenas a geometria estática para evitar que tiros válidos sejam rejeitados devido à latência. Adicionalmente , você pode querer implementar validações adicionais do lado do servidor da seguinte forma:
Acompanhe quando o jogador último disparou sua arma e valide para garantir que não está atirando muito rápido.
Acompanhe a quantidade de munição de cada jogador no servidor e confirme que um jogador atirador tem munição suficiente para executar o ataque de arma.
Se você implementou equipes ou um sistema de combate "jogadores contra bots", confirme que o personagem atingido é um adversário / inimigo, não um companheiro de equipe.
Confirme que o jogador atingido está vivo.
Armazene o estado de arma e jogador no servidor e confirme que um jogador atirador não está bloqueado por uma ação atual, como recarregar ou um estado como correr.
Manipulação de armazenamento de dados
Em experiências usando DataStoreService para salvar dados do jogador, os exploiters podem aproveitar dados inválidos data e métodos mais obscuros, para impedir que um DataStore salve corretamente.Isso pode ser especialmente abusado em experiências com negociação de itens, mercados e sistemas semelhantes em que itens ou moedas deixam o inventário de um jogador.
Garanta que quaisquer ações realizadas através de um RemoteEvent ou RemoteFunction que afetem os dados do jogador com a entrada do cliente sejam sanitizados com base no seguindo:
- Instance valores não podem ser serializados em um DataStore e falharão. Utilize validação do tipo para evitar isso.
- DataStores tem limites de dados .Fibras de comprimento arbitrário devem ser verificadas e/ou limitadas para evitar isso, ao mesmo tempo em que se garante que chaves arbitrárias ilimitadas não possam ser adicionadas às tabelas pelo cliente.
- Índices de tabela não podem ser NaN ou nil. Iterar sobre todas as tabelas passadas pelo cliente e verificar se todos os índices são válidos.
- DataStores só pode aceitar caracteres UTF-8 válidos, então você deve sanitarizar todas as strings fornecidas pelo cliente via utf8.len() para garantir que elas sejam válidas. utf8.len() retornará o comprimento de uma string / cadeia / texto, tratando os caracteres de idioma como um único personagem; se um caractere UTF-8 inválido for encontrado, ele retornará nil e a posição do personagem inválido.Observe que strings inválidas do UTF-8 também podem estar presentes em tabelas como chaves e valores.
Atrasamento remoto
Se um cliente for capaz de fazer com que seu servidor complete uma operação cara em termos de custo ou acesse um serviço limitado em taxa como DataStoreService através de um RemoteEvent, é crítico que você implemente limitação de taxa para garantir que a operação não seja chamada com muita frequência.A limitação de taxa pode ser implementada rastreando quando o cliente invocou remotamente um evento remoto pela última vez e rejeitando o próximo pedido se for chamado muito cedo.
Validação de movimento
Para experiências competitivas, você pode querer validar os movimentos do personagem do jogador no servidor para garantir que eles não estejam se teletransportando ao redor do mapa ou se movendo mais rápido do que o aceitável.
Em incrementos de 1 segundo, verifique a nova localização do personagem contra uma localização previamente armazenada.
Compare a diferença de distância real contra a diferença tolerável e proceda da seguinte forma:
- Para uma tolerância aceitável, cache a nova localização do personagem em preparação para a próxima verificar / conferirincrementada.
- Para um delta inesperado ou intolerável (vulnerabilidadede velocidade/teletransporte potencial):
- Incrementar um valor separado de "number of offenses" para o jogador, versus penalizá-lo por um "false positive" resultante de latência extrema do servidor ou outros fatores não exploráveis.
- Se um grande número de ofensas ocorrer em um período de 30 a 60 segundos, Kick() o jogador da experiência inteira; caso contrário, redefina a contagem de "number of offenses".Observe que, ao expulsar um jogador por fraude, é melhor prática registrar o evento para que você possa acompanhar quantos jogadores são afetados.