Best way to protect RemoteEvents against exploiters?

Could you explain why you think my idea doesn’t protect me from anything? I’m pretty new with securing remote events and I would appreciate your feedback on this.

1 Like

It won’t secure because it doesn’t have a point, also it might affect the performance too. As it doesn’t have a point, let’s make a point for it! So, yes, we use RemoteEvents but we have spammers (Ex: ShopGUI / Player is spamming the buy button.) and we need to prevent spamming. This is my method.

local PlayersThatFiredRemote = {}
local Remote = game.ReplicatedStorage.Remote
Remote.OnServerEvent:Connect(function(plr)
wait(0.5)
if table.find(PlayersThatFiredRemote,plr.UserId) then return
else
table.insert(PlayersThatFiredRemote,plr.UserId)
-- stuff here
wait(0.5)
table.remove(PlayersThatFiredRemote,plr.UserId)
end
end)

Since player can activate a remoteEvent when they want, your method won’t have a point. But I saw your method, transformed it to anti-spam remote system. And I don’t advice that method to you. I think you’ve tried to make an anti-spam but, it might affect the server. Because of this, I’ve transformed it, but it’s working with tables! I’m advicing you to work with something like that.

3 Likes

There is no way to completely protect events, but you can make it harder for exploiters.

1 Like

When trying to understand where an exploiter sits at in your game, it can be difficult, but it doesn’t need to be.

Do sanity checks!

Cannot stress this enough. If you’re making a system that is sort of like an admin GUI, make it so the client just fires the server (ofc add a denounce so their firing is rate limited) and the server receives the information, and checks your userid. That server script would have a table of userids that are allowed to use the gui, and the script will check the specific players UserId. If their UserId doesn’t exist in the table, the best thing to do is to kick the person who fired because no average player would be trying to fire hidden remotes and stuff.

Personally what I do that is a complete show stopper for any exploiter, is turning their own executor against them. This might sound stupid, but all executors don’t actually have names when they inject code.

An executor is basically just a local script that they manually fire, so what I do on the server is keep the name of my local script into account, and when I fire I check the name of the script that fired in their player scripts folder. That way if an exploiter tries to fake their scripts name (keep in mind executors script names are just “LocalScript” the client would have told different information from what the server sees so it won’t continue, since nothing client sided replicates to server boundary.

Then again…

There is no true way to stay safe from any form of exploit. Bypassing things can be easy, but when you have things like UserId checks and stuff that completely destroys any chance of any exploiter getting what they want out of you, you pretty much did a good job at that point. However, Userids don’t work for everything unless it’s restricted.

5 Likes

You physically can’t solve this (just do honeypotting and server side checks.

1 Like

Ok, I’ve used this way to secure my remotes. I have made a passcode where it fires with the remote. For example, the code = 8323f8929gj92gv2g and then I make it do FireServer(remote,code). and when It gets the server function it makes it check if the code is equal to the code else It detects that a exploiter has tried breaching the remotes.

1 Like

@Necrozatus

This system won’t work on the majority of exploiters since they can view what the code is since it’s stored on their device.

1 Like

These are some of the things I do to make RemoteEvents more difficult for exploiters to make unintended use of.

  1. I don’t publish my games with RemoteEvent instances in ReplicatedStorage, I have the server create them all at startup. I define all my remote events in a Lua table in a ModuleScript in ServerScriptService. This allows for some simple exploiter countermeasures I detail below.

  2. All of my RemoteEvents are uni-directional. By this I mean that any particular RemoteEvent instance is only used for either Client-to-Server or Server-to-Client communication, never both. This is configured in the server-side Lua table, so that only the server knows a RemoteEvent’s direction. Then, on the server, all Server-to-Client RemoteEvents are connected to an OnServerEvent listener that is only used to detect exploiters. Anyone firing a RemoteEvent in the wrong direction gets kicked and banned, since it’s only possible to do this by exploiting. Exploiters love to just loop over ReplicatedStorage and rapid-fire all the remotes they find, hoping one of them does something to break the game. If you’re really nasty, you can include decoy events that your game doesn’t really use but have enticing names like “GrantCoinsEvent” or “KickPlayerEvent” and just bind them right to your exploit detection code.

  3. All RemoteEvents are strictly validated. Number, type, numerical range and context of all RemoteEvent parameters are defined in my server-side table and are validated. I put context in bold because this is something a lot of developers don’t think about including. What I mean by this is validation of whether or not an incoming RemoteEvent is expected based on the state of the server. For example: an event that a player has fired their gun might only be valid during the gameplay round of a FPS, not during intermission when players are in the lobby. If your game server has a state machine, you can use this to define which states a RemoteEvent is valid for, where this makes sense.

  4. RemoteEvents for admin commands are only replicated to players who have already been authenticated by the server as admins (by UserId). This is done by creating per-user RemoteEvents for each admin player and parenting them to their PlayerGui so that they only replicate to that player. LocalScripts that use these remotes are likewise provided only to these players. This way, exploiters don’t even know that admin UI and events exist, they never get them.

  5. My RemoteEvents server table also has per-event rate limits. The server has thresholds and “leaky bucket” counters for each event. If someone is firing an event 10 times per second, and I know it’s something that a legitimate client will only send once per minute, I can flag it as exploiting. The leaky bucket approach prevents false positives due to packet timing irregularity.

I generally don’t bother with client-side obfuscation like having the server randomize the names of RemoteEvents and send the client a manifest (which is the effectively the same as generating passwords and then sharing them with the client). This complicates client code and won’t thwart determined exploiters, only slow them down a bit.

37 Likes

If that’s the case, you must encrypt your code since most of the exploiters won’t know what you’ve done.

2 Likes

Encrypting your code will only delay the exploiter.

1 Like

Yes, you will have more chances of a secure game if remotes will be fired at a certain time. You must also have it generate a random key with encryption every time the remote is going to be fired. Now if that doesn’t help you that way, you can use remote functions which get data from another side and return it back to your side. How is this helpful? You can have your encrypted code in the Main script where exploiters won’t have the power to do so and from the main script, It will return back to the client.

1 Like

What do you mean generate key every remote fires? It would deny next time since client key is different between server.

There is RemoteSpy which receives infomation eath client got fired and got called. Exploiters would know key instantly.

1 Like

I “solved” this age old problem with a sort of elegant solution. While you can never truly secure RemoteEvents, the solution I came up with will deter most of the exploiters who just watch YouTube videos on how to haxx adopt mee and have no real programming experience, or any idea of what they’re doing in general. I’ll outline my systems below:

Event name scrambling

This one is pretty common. This involves storing a cache of the RemoteEvent objects alongside the original names somewhere secure, like the server. Then ever second or so you rename each event to a GUID. This along deters most exploiters because while they can still see the traffic of the RemoteEvents, the names will always be changing, making it incredibly hard to track down specific events.

In my application the server holds the master list between the original event name, and it’s physical object in ReplicatedStorage. The server is also the one scrambling the names. This ensures the clients can’t stop the scrambling. When the client joins the game, it invokes one remote function that isn’t scrambled. This function returns the list of RemoteEvents and their objects. The server then marks that they’ve called it and won’t allow them to call that again.

Note: I use a custom NetworkingService that ties all these things together.

Rotating key argument

This one is fantastic. This involves the internal NetworkingService attaching a GUID alongside the event that’s fired. This is different than a password because those aren’t secure and are genuinely pointless. Every time an event is fired, the server returns a new GUID. It expects this to be send back when that event is fired again. If they don’t match, the server throws out the request. These GUID’s are unique to each RemoteEvent and rotate each time they invoke.

This is important because while the GUID’s are visible to the exploiters*, they are meaningless without an explanation. If a key is sent more than once, the events won’t go through.

Note: Upon testing this solution with Synapse, I found that whatever version of RemoteSpy I was using failed to present the data returned in a RemoteFunction. I was able to see the initial request and it’s data, but anything returned by the server was not visible. If this is the case with all current versions of RemoteSpy, it’s essentially impossible to know what they next GUID is.

Things to know

You cannot store anything in a module. The list of RemoteEvents to Objects, the master GUID table on the client etc. All of these things must be stored in private variables so they’re not accessible by any malicious actors.

Final remarks

Like I said at the beginning, you can never truly secure RemoteEvents / RemoteFunctions. These solutions just deter a lot of malicious actors from perusing more exploits in your game. I only recommend these solutions if you’re experienced with writing internal libraries such as NetworkingServies and whatnot. These systems can fail incredibly easily if not setup properly. I’ve fine tuned my module to work reliably, without problems. If you have any questions please let me know.

16 Likes

That would then go back to my second argument of encrypting the key

1 Like

All client side anti-exploit measures are bad at stopping exploiters. I’m 99% sure that they have or are figuring out your key system right now. The best anti-exploit measure would be not relying on the client in the first place.

2 Likes

For some reason this got bumped even though the last reply was more than 2 weeks ago.

If you want to stop exploiters from spamming remote events, I found a solution.

--Localscript

local SendDelay = 0.1

while true do
    Remote:FireServer(..., workspace:GetServerTimeNow())
    task.wait(SendDelay)
end

--Server script

local SendDelay = 0.1

local ServerStartTick = workspace:GetServerTimeNow()

local PlayerLastTick = 0

Remote.OnServerEvent:Connect(function(Player, Tick)
    --Is the client not waiting enough?
    if Tick - PlayerLastTick < SendDelay then
        Player:Kick("Remote spam!")
    end
    
    --Is the client trying to break the system by sending a value ahead of the server?
    if Tick > workspace:GetServerTimeNow() then
        Player:Kick("Back from the future?")
    end
    
    --Is the client acting like it started running BEFORE the server?
    if Tick < ServerStartTick then
        Player:Kick("Back from the past?")
    end
    
    --Less accurate, they could just have had a ping spike, but better to try and cover everything
    if workspace:GetServerTimeNow() - Tick > 5 then
        Player:Kick("Your internet connection is TOO unstable!")
    end
end)
3 Likes

I’ll give you secure example

Add a boolvalue in character model
everytime you pick up a coin make it true through what ever method you use to pick up the coin
(Preferably not .touched)

then fire the remoteevent making sure to check if boolvalue is true when the remoteevent is onServerEvent before proceeding to add the coin

Let’s say you’re making a script inside a part that checks whether you clicked on it or not with your mouse (Local > Server).

You also want to add a distance the click should be, so let’s say 10 studs.

The way this RemoteEvent can be manipulated is that it can bypass the distance and can just click anything interactable on the server.

The solution is to actually check if the player is 10 studs or closer to that part, in order to click, in the server.

In hindsight, just add extra security in the server, and a more useful addition to this can be to kick the player using the variable player from the RemoteEvent, if the 10 stud radius is out of reach from the player.

What if your legit local scripts use a remote event every RunService RenderStepped? That puts limitations.

It… shouldn’t? If someone uses an FPS unlocker and has a really good PC, people could really easily DDoS the server without even realizing.