O Roblox usa um sistema de física distribuída em que os clientes têm a 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 Lua arbitrário no cliente para manipular o modelo de dados do cliente e descompilar e ver o código executando em seu.
Coletivamente, isso significa que um hacker habilidoso pode potencialmente executar código para trapacear em seu jogo, incluindo:
- Teletransportando seu próprio personagem ao redor do local.
- Ativando unsecured RemoteEvents ou invocando RemoteFunctions, como para recompensar-se por ganhar itens sem ganhá-los.
- Ajustando sua personagem's WalkSpeed para que ele se mova muito rápido.
Embora você possa implementar defesas de design limitadas para detectar ataques comuns, é altamente recomendável que você implemente mais táticas de mitigação do lado do servidor, pois o servidor é a autoridade final para qualquer experiência em execução.
Táticas de Design Defensivo
Decisões de design básicas podem servir como medidas de segurança "passo a passo" para desencorajar exploits. Por exemplo, em um jogo de tiro em que os jogadores obtêm pontos por matar outros jogadores, um hacker pode criar um monte de bots que se teletransportam ao mesmo local para que eles possam ser rapidamente mortos por pontos. Dado este potencial vulnerabilidade, considere duas abordagens e seu resultado previsível:
Aproximação | Resultado Previsível |
---|---|
Persiga bots escrevendo código que tenta detectá-los. | |
Reduza ou elimine completamente os ganhos de pontos por eliminações em jogadores recém-criados. |
Embora o design defensivo obviamente não seja uma solução perfeita ou abrangente, ele pode contribuir para uma abordagem de segurança mais ampla, juntamente com mitigação do lado do servidor.
Mitigação do Lado do Servidor
Possível, mas não impossível, o servidor deve dizer o veredicto final sobre o que é "verdadeiro" e o que é 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 valorizar e aprovar cada uma dessas alterações/ações antes que os resultados sejam replicados para outros jogadores.
Com a exceção de certas operações de física, as alterações no modelo de dados no cliente não se replicam para o servidor, então o caminho principal de ataque geralmente é via os eventos de rede que você declarou com RemoteEvents e RemoteFunctions. Lembre-se de que um exploiter executando seu próprio código em seu cliente pode invocar essas com qualquer dado que eles desejam.
Verificação de tipo de execução remota
Um caminho de ataque é para um hacker invocar RemoteEvents e RemoteFunctions com argumentos do digitarerrado. Em alguns cenários, isso pode causar código no servidor ouvindo esses controles remotos de uma maneira que seja vantajosa para o hacker.
Ao usar eventos/funções remotos, você pode evitar esse tipo de ataque ao validar os tipos de argumentos passados no servidor. O módulo t, disponível aqui, é útil para verificar se o código do módulo existe como um 2> Class.ModuleScript2> chamado t
LocalScript em StarterPlayerScripts
local ReplicatedStorage = game:GetService("ReplicatedStorage")local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")-- Passe a cor e posição da peça 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 remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
local t = require(ReplicatedStorage:WaitForChild("t"))
-- Crie um validador de tipo com antecedência para evitar o sobreposição não necessária
local createPartTypeValidator = t.tuple(t.instanceIsA("Player"), t.Color3, t.Vector3)
-- Crie uma nova peça com as propriedades passadas
local function createPart(player, partColor, partPosition)
-- Verifique se os argumentos passados são válidos
if not createPartTypeValidator(player, partColor, partPosition) then
-- Retorne silenciosamente "false" se o tipo check falhar aqui
-- Aumentar um erro sem um cooldown pode ser abusado para derrubar 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
-- Vincular "createPart()" ao retorno de chamada da função remota
remoteFunction.OnServerInvoke = createPart
Validação de Dados
Outro ataque que os exploiters podem lançar é enviar tipos técnicamente válidos mas fazê-los extremamente grandes, longos ou de outra forma malformados. Por exemplo, se o servidor tiver que executar uma operação cara em uma string que escala com comprimento, um exploiter pode enviar uma string incrivelmente grande ou malformada para bogar o servidor.
Da mesma forma, tanto inf e NaN irão type() como 1> number1>, mas ambos podem causar problemas principais se um explorer os enviar e eles não foremem tratados corretamente através de funções como as seguindo:
local function isNaN(n: number): boolean
-- NaN nunca é igual a si mesmo
return n ~= n
end
local function isInf(n: number): boolean
-- Número pode ser -inf ou inf
return math.abs(n) == math.huge
end
Outro ataque comum que os exploiters podem usar envolve enviar tables em vez de um Instance . Pontos de carga complexos podem imitar o que seria uma referência de objeto normalmente.
Por exemplo, fornecido com um sistema de loja na experiência onde os dados do item, como preços, são armazenados em NumberValue objetos, um hacker pode contornar todos os outros testes 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 moedas ao invés de levá-las!},}-- Envie um payload malicioso para o servidor (isso será rejeitado)print(buyItemEvent:InvokeServer(payload)) -- Produz "false Item fornecido inválido"-- Envie um item real para o servidor (isso vai 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 está spoofed e está na pasta 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 exemplo de fluxo abaixo
end
-- Vincular "buyItem()" à chamada retorno de chamada
buyItemEvent.OnServerInvoke = buyItem
Validação de Valor
Além de validar tipos e dados, você deve validar os valores passados por 1> Class.RemoteEvent|RemoteEvents1> e 4> Class.RemoteFunction|RemoteFunctions4>, garantindo que eles sejam válidos e lógicos no contexto solicitado. Dois exemplos comuns são
Loja In-Experience
Considere um sistema de loja na experiência com um menu de seleção de produto com um botão "Comprar". Quando o botão for 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.
Objetivando Armas
Cenários de combate exigem atenção especial para validar valores, particularmente através da validação de mira e acerto.
Imagine um jogo onde um jogador pode disparar um feixe de laser para outro jogador. Em vez do cliente dizer ao servidor quem para danificar, ele deve instead dizer ao servidor a posição de origem do tiro e a posição/posição que ele pensa que atingiu. O servidor pode então validar o seguindo:
A posição que o cliente relata atirar de está perto do personagem do jogador no servidor. Observe que o servidor e o cliente diferirão um pouco devido à latência, então será necessário aplicar uma tolerância extra.
A posição que o cliente relata golpeando está razoavelmente perto da posição da parte que o cliente relata ter golpeado, no servidor.
Não há obstruções estáticas entre a posição que o cliente relata estar atirando e a posição que o cliente relata estar atirando. Este check garante que um cliente não esteja tentando atirar através das paredes. Observe que isso só deve verificar geometria estática para evitar chutes válidos rejeitados devido a latência. Além disso , você pode querer implementar validações de lado do servidor adicionais, como a seguinte:
Rastreie quando o jogador acertou sua arma pela última vez e valide para garantir que eles não estejam atirando muito rápido.
Rastreie a quantidade de munição de cada jogador no servidor e confirme que um jogador de tiro 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 hit é um adversário / inimigo, não um colega de equipe.
Confirme se o jogador atingido está vivo.
Armazenar o estado do usuário e do armazenamento no servidor e confirmar que um jogador em chamas 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 Class.DataStoreService ', e métodos mais obscuros, para impedir que um Class.DataStore seja salvo corretamente. Isso pode ser abusado especialmente em experiências com negociação de itens, mercados e métodos semelhantes, onde os itens ou moedas deixam o inventário
Certifique-se de que quaisquer ações executadas por meio de um RemoteEvent ou RemoteFunction que afetam os dados do jogador com a entrada do cliente são sanitizadas com base nas seguindodiretrizes:
- Instance valores não podem ser serializados em um DataStore e falharão. Utilize validação de tipo para evitar isso.
- DataStores têm limites de dados. Strings de comprimento arbitrário devem ser verificados e/ou limitados para evitar isso, ao mesmo tempo que garante que as chaves arbitrárias de comprimento não possam ser adicionadas às tabelas pelo cliente.
- Os índices de tabelas não podem ser NaN ou nil . Iterate sobre todas as tabelas passadas pelo cliente e verifique se todos os índices estão válidos.
- DataStores só pode aceitar caracteres UTF-8 válidos, então você deve sanificar todos os caracteres fornecidos pelo cliente via utf8.len() para garantir que eles se
Acelerador Remoto
Se um cliente puder fazer seu servidor completar uma operação de cálculo de alto custo ou acessar um serviço de taxa limitada, como DataStoreService via um RemoteEvent, é crucial que você implemente limite de taxa para garantir que a operação não seja chamada com muita frequência. O limite de taxa pode ser implementado rastreando
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 teletransportando ao redor do mapa ou se movendo mais rápido do que o normal.
Em incrementos de 1 segundo, verifique a nova localização do personagem em relação a um local previamente armazenado.
Compare a distância real contra a tolerável e siga o procedimento a seguir:
- Para um delta tolerável, armazene a nova localização do personagem em preparação para a próxima verificar / conferirincrementada.
- Para uma Delta inesperada ou intolerável (vulnerabilidadede velocidade/teleporte potencial):
- Incrementa um valor separado "number of offenses" para o jogador, em vez de puni-lo por um "false positive" resultante de latência extrema ou outros fatores de não-exploit.
- Se ocorrer um grande número de ofensas em um período de 30-60 segundos, Kick() o jogador da experiência inteiramente; caso contrário, redefina o contador de "number of offenses". Observe que ao expulsar um jogador por cheating, é melhor prática registrar o evento para que você possa rastrear quantos jogadores são afetados.