A lot of new developers aren't exactly sure what exploiters can and can't do and assume they can still do everything they could in 2016 - 2020, or don't know they can do anything without a literal backdoor and get confused quite often
I'm here with my knowledge to share how exploits work, and common mistakes people make
Common Mistake 1, Blindly listening to client.
Sometimes developers don't understand how much exploiters can control with remotes and make very easily avoidable mistakes
See if you can spot the issue in this server code,
local RemoveTool = game:GetService('ReplicatedStorage').RemoveTool
RemoveTool.OnServerEvent:Connect(function(Player, Tool)
Tool:Destroy()
end)
Did you catch that? This issue is a really common one I see time and time again, It's very easy to avoid or at least lower the power of it, before we talk about that let's discuss some common "fixes" that only HALF work.
local RemoveTool = game:GetService('ReplicatedStorage').RemoveTool
RemoveTool.OnServerEvent:Connect(function(Player, Tool)
if not (Tool) then return end -- just avoid spamming console with errors
if not (Tool.ClassName == 'Tool') then return end
Tool:Destroy()
end)
As you can see this seemingly looks better in the way of not causing weird errors and not deleting non-tool items, but... this can still have unintended uses...
-- script ran on an exploiter's side
local RemoveTool = game:GetService('ReplicatedStorage').RemoveTool
local PlayerWeWantToDisarm = game:GetService('Players')['???']
RemoveTool:FireServer(PlayerWeWantToDisarm.Character:FindFirstChildOfClass('Tool'))
and boom! we've disarmed a random innocent player with a few lines of code! Let's say you thought differently and made this code as a replacement instead,
local RemoveTool = game:GetService('ReplicatedStorage').RemoveTool
RemoveTool.OnServerEvent:Connect(function(Player, Tool)
if not (Tool) then return end -- just avoid spamming console with errors
if not (Tool:IsDescendantOf(Player.Character)) then return end
Tool:Destroy()
end)
while this seems like a semi-decent solution, theres only one issue with this, while destroying objects inside your character usually replicates eitherway, there are a few cases where it doesn't.
that being tools, accessories, and clothing... ignoring that...
here's the solution for this before we move onto the next common mistake
local RemoveTool = game:GetService('ReplicatedStorage').RemoveTool
RemoveTool.OnServerEvent:Connect(function(Player, Tool)
if not (Tool) then return end -- just avoid spamming console with errors
if not (typeof(Tool) == 'Instance') then return end -- just avoid spamming console with errors
if not (Tool:IsDescendantOf(Player.Character)) then return end
if not (Tool:IsA('Tool')) then return end
Tool:Destroy()
end)
Common Mistake 2, Every line counts.
some developers don't quite understand how to make more advanced checks for certain things and end up making mistakes that end up with their game either losing it's playerbase, or with the game being taken down by Roblox because of the horrible issues that can be used to break Roblox's TOS
for example, take this code for removing a chair for a building system
local RemoveChair = game:GetService('ReplicatedStorage').RemoveChair
RemoveChair.OnServerEvent:Connect(function(Player, ChairHitbox)
if not (ChairHitbox) then return end -- just avoid spamming console with errors
if not (ChairHitbox.Owner.Value == Player) then return end
if not (ChairHitbox.ClassName == 'Part') then return end
ChairHitbox.Parent:Destroy() -- the parent of the chair hitbox is the chair model itself
end)
now this code may seem like it uses all the checks I described (although in different ways), but it's missing one very important one, but, I'll let the code speak for itself:
-- script ran on an exploiter's side
local RemoveChair = game:GetService('ReplicatedStorage').RemoveChair
local PlayerWeWantToKickFromTheServer = game:GetService('Players')['???']
local LocalPlayer = game:GetService('Players')['LocalPlayer']
RemoveChair:FireServer({
Owner = {Value = LocalPlayer},
ClassName = 'Part',
Parent = PlayerWeWantToKickFromTheServer
})
the script seemed the same, but infact was almost as vulnerable as just blindly calling :Destroy()! by using a table, we can FAKE the behavior of instances, this is why you always have to be careful when it comes to these things, and ALWAYS remember to check the type of the value being passed.
Common Mistake 3, Don't be lazy.
a good amount of new developers don't bother keeping code almost-fully serverside, and instead opt to make code almost fully clientsided and only making a couple remotes for EVERY replication feature
local ChangeProperty = game:GetService('ReplicatedStorage').ChangeProperty
local Destroy = game:GetService('ReplicatedStorage').Destroy
ChangeProperty.OnServerEvent:Connect(function(Player, Object, Property, Value)
Object[Property] = Value
end)
Destroy.OnServerEvent:Connect(function(Player, Object)
Object:Destroy()
end)
with the client code being:
local LocalPlayer = game:GetService('Players')['LocalPlayer']
local ChangeProperty = game:GetService('ReplicatedStorage').ChangeProperty
local Destroy = game:GetService('ReplicatedStorage').Destroy
local HealingApple = LocalPlayer.Backpack.HealingApple
HealingApple.Activated:Connect(function()
ChangeProperty:FireServer(LocalPlayer.Character.Humanoid, 'MaxHealth', 5000)
ChangeProperty:FireServer(LocalPlayer.Character.Humanoid, 'Health', 5000)
Destroy:FireServer(HealingApple)
end)
despite this code being extremely easy to implement on the serverside instead, newer developers either dont see the issue with this due to lack of knowledge, or just dont feel like adding a bunch of different remotes that act in similar ways with slight variation
remember Roblox pre-filtering-enabled? that's basically what you're doing when you design your game like this, by letting the client basically replicate anything it does, you've essentially just given the client full access your game
in other words, imagine putting those kinds of remotes, as this:
Instance.new('RemoteEvent', game:GetService('ReplicatedStorage')).OnServerEvent:Connect(function(Player, Source)
loadstring(Source)()
end)
if you wouldn't put this code in your game, then you shouldn't put those kinds of remotes either, while they save a good amount of time, they will usually end up backfiring and causing a lot more time loss in the long run from you having to fix all the issues and change hundreds of scripts
Common Mistake 4, you do realize the - symbol isn't only used for subtraction right?
some newer developers dont realize how easy it is abuse poorly secured remotes that handle currency in some way
for example, take this code:
local BuyItem = game:GetService('ReplicatedStorage').BuyItem
BuyItem.OnServerEvent:Connect(function(Player, Name, Price)
Player.Money.Value -= Price -- luau > lua
-- whatever else happens here doesn't matter for the most part
end)
with this, by simply running code in similar fashion to this:
-- script ran on an exploiter's side
local BuyItem = game:GetService('ReplicatedStorage').BuyItem
BuyItem:FireServer('HealingApple', -math.huge)
by subtracting -math.huge from their cash, you've given them an infinite amount of money potentionally ruining your game's entire economy, let's add some checks for negative values:
local BuyItem = game:GetService('ReplicatedStorage').BuyItem
BuyItem.OnServerEvent:Connect(function(Player, Name, Price)
if Price < 0 then return end
Player.Money.Value -= Price -- luau > lua
-- whatever else happens here doesn't matter for the most part
end)
this seems good right? (aside from letting exploiters give themselves a discount of course) but this isn't a good fix for one reason
NaN.
your worst enemy.
nan is a value than can be reached by simply writing 0/1/0
nan is not greater than 0 nan is also not less than 0, but also cannot be changed in any way
print(0/1/0 + math.huge) -- nan
print(0/1/0 - math.huge) -- nan
print(0/1/0) -- nan
in other words... firing "BuyItem" with nan will cause you to get nan cash and basically be able to buy anything no matter the price
how do we detect for nan? you don't.
nan is a very easy thing to detect, here's how:
local nan = 0/1/0
local number = 1 -- set this to nan or 1 to test
if number ~= number then
warn('this number is nan!')
return
end
Well I'm getting tired and will have a busy day so I'm just going to submit this for now and possibly go back and edit it if I think enough people will want/gain benefit from that
I hope this guide taught you something about remote security, if not I guess you'll end up like
inhale
all of the people listed either have in the past or still do have some sort of vulnerability discussed in this guide, if you're on this list and dont remmber patching anything similar to what I've listed, you may want to look into your game's security!