Secure your RemoteEvents/RemoteFunctions

a guide on how to avoid ruining your game with under 5 lines of code!

by 0xFE0F

Author Avatar

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.

image|50x50

That's a joke lads.


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

The 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

(please dont harass these users) (also these are all Roblox usernames)

@RoyStanford (Upsilon Library in general) @GianLestyr_924(Work at a pizza place [Old], like, all of it) @Q_Q (Handless segway tool) @mygame43(Elemental Wars) @Craythrayscientist (Gas Station Simulator) @Sssqd (Cook Burgers)

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!

View in-game to comment, award, and more!