Stop Building Exploits Into Your Game: A Lesson On How To Secure Your Server

Stop Building Exploits Into Your Game: A Guide On How To Secure Your Server

Hi, My name is jakebball11 and I have been on this platform since 2012 and have been programming for roughly 2 1/2 years. Due to this I have made many mistakes in terms of game security which I hope to show you how to avoid so you dont have to deal with the endless headaches of data exploits.

Prerequisites

This guide is not complicated by any means but since this guide is naturally about securing your server model I am going to assume you have a solid understanding of the Roblox Client-Server Model and how they communicate.

Another thing to understand which is not full made clear in the article is how client data can be changed by exploiters. For example, a remote event the is detect on the server can have data sent to it from the client that was faked. Im sure this won’t cause any isssues down the line so lets move on…

Whats the big deal

Let me give you a common scenario a lot of new programmers have to deal with when learning to use remote events and datastores. Say you make a cool simulator where you scrub cars to get money in which you can buy better tools such as a bigger sponge. You programmed it compelely and are ready for launch so you make it public and let it rip.

  • Day 1, Launch goes great and you are aquiring a solid player base with a good engaging community!

  • Day 2, Its the same as before except now, the players are starting to gain enough money to be on the leaderboard you have setup in game!

  • Day 3, you check in game and see someone has an item that is relatively difficult to get this early but it might be they are just dedicated so you ignore it for now.

  • Day 4, you spot your first data exploiter in game as they now have 100x the amount of coins the top player has even though the exploiter just started playing. You ban him and move on, problem solved, right? Not quite, your code already has a security issue with it and everybody knows it!

  • Day 5, the amount of exploiters grow increasingly large due to the lack of security on the server code and now your community wants answers. You try to hire moderators which works ok but the rate at which exploiters are joining the game naturally the moderation team is overwhelmed.

  • Day 6, Your player base is slowly going down while the exploiters only increase.

  • Day 7, your game is now exploiter full and is basically dead at this point.

What Was The Problem?

Now, what was the issue with that games code? You didnt properly secure the code on the server! Often times what this means is you created remote events that set the server data directly. Lets look back at our previous example. Say this is the client code that gives you coins after you washed your
car.

Client


local Car1Coins = 5

player.Car1WashedEvent:Connect(function()
   if player.OwnsCar1.Value == true then
       GiveCoinsRemote:FireServer(Car1Coins + player.Coins.Value)
   end
end)

Server

GiveCoinsRemote.OnServerEvent:Connect(function(player, coins)
     player:SetData("Coins", Coins)
end)

What do you notice about this client code that is wrong? It seems fine right, you check if they own the car and tell the server to give them the coins and we all live happily ever after in a secure world. Not quite.

Remember how I said the client can fake data? An exploiter can simply changed the Car1Value on the client, bypass all the if statements, and then fire the remote events directly.

Now an exploiter can fire it hundreds of times for quick and easy coins even if they dont own the car! As you can see, you programmed the exploit right into the game!

One good way to think about this is as a bank! In this first image we have the bank having a customer wanting to withdraw 5 Million Bucks.

What do you notice about this transaction? The bank didnt check if he had the money! Imagine if bank security worked like this in real life? It would essentially be desiging theft into the banking system. This is what they should be doing instead.

As you can see, checking the amount of money is crucial to a banks secuity. This same principle applies to roblox security. In this case, the server is the bank and the client is the customer. In the bad client code just above the analogy you can see how we arent checking the server to see if they have enough just like how the bad bank analogy isnt checking if the customer has enough.

This is building an easy way to exploit the game’s coins system directly in the code! What should you do? Well, what does the bank do? They have the teller check if they have the amount before any withdrawl. In this case, you would want the server to check if the client owns the car before giving them the money like so.

Client

player.Car1WashedEvent:Connect(function()
     CarWashedRemote:FireServer()
end)

Server

local Car1Coins = 5

GiveCoinsRemote.OnServerEvent:Connect(function(player)
    if player.OwnsCar1Value == true then
      player:SetData(player.Coins + Car1Coins)
    end
end)

As you can see the server is now checking if he owns the car itself instead of blindly trusting the client. If you do this correctly it should be virtually impossible for any exploiters to give themselves any currency or data.

Now, you might think, “cant they spam it now that they own the car giving a lot of money as well?”. Correct! Which is why we need to add debouncing!

Debouncing

What is deboucing? Debouncing is a fancy word for using a bool value to limit time between events. In this case we would need to make a bool value. Since we need one for each player you can either use a table with the values for each player or a BoolValue inside the player instance. We will use the latter for simplictiy sake.

Server

local Car1Coins = 5

GiveCoinsRemote.OnServerEvent:Connect(function(player)
    if player.OwnsCar1Value == true then
      if player.Debounce.Value == false then
         player.Debounce.Value = true
         player:SetData(player.Coins + Car1Coins)
      end
    end
end)

What this is is doing is checking if the debounce value is false. If it is, it makes it true so the player can fire the remote event as much as he wants but wont allow him to give the coins more then once even if he owns the car itself.

This is cool but obviously we want them to scrub the car multiple times. So what we can do is add wait(3) to the end and set it back to false like so.

Server

local Car1Coins = 5

GiveCoinsRemote.OnServerEvent:Connect(function(player)
    if player.OwnsCar1Value == true then
      if player.Debounce.Value == false then
         player.Debounce.Value = true
         player:SetData(player.Coins + Car1Coins)
         task.wait(3)
         player.Debounce.Value = false
      end
    end
end)

Now, even if he owns the car, he can spam the remote event all he wants but will still be limited to only one coin increment every 3 seconds! The magic of deboucing!

This is the basic idea of server security. ANY transaction of any sort regarding player data or stats should ALWAYS be handled on the server. This is why the term “Never Trust The Client” is used all over the programming world including outside of Roblox as the client can send any data it wants including false data the an exploiter wants to pretend he has.

closing

This is a very simple example but should get the point across good enough. This is also my first large tutorial I made so if you have any advice make sure to let me know in the replies! :slight_smile:

If you have any questions or want to make improvements to this post feel free to PM me as well!

57 Likes

Can’t a exploiter spam fire the RemoteEvent if they own the car?

3 Likes

Couldn’t the exploiter then just fake the player value passed on the event?

1 Like

no, remote events are given the player value by default. Its impossible for an exploiter to prevent that from happening as its passed as a paremeter by default

2 Likes

Your right actually. I will add a debounce section to solve that since I thought it would be implied. Thanks for the input.

2 Likes

That’s a relief for my admin panel! I was securing my remotes with a UserId check for the calling player, but because of my paranoia, I generate a pre-shared key which was a bunch of GUIDs strung together It was >10 GUIDs together. Good thing I won’t need that.

2 Likes

An admin panel? That shouldn’t be even viewable to the client at all.

I think you should have scripts under the admin panel that operate on it, so that it’s only activated when it’s parented to a space that can run those scripts, like a player’s ‘PlayerGui’ for example. So you can put the panel in ‘ServerStorage’, and then listen for players to join on the server, and if their id matches up, parent the admin panel to their gui, this way clients that are not admins or anyone authorized do not even see the existence of the admin panel. As such, you don’t have to worry about your panel’s remote security

1 Like

I already did that, albeit slightly differently. I parent the panel in my server-sided Script in ServerScriptService, but the rest is identical.

A hacker could still fire the remotes without the panel, as they’re located in ReplicatedStorage, which is replicated to clients.

I am pretty sure if you clone the UI from server you can have remoteevent inside of it so it will be parented directly to your UI and only Server and you are gonna see it which allows you to have “not replicated remote to all clients”.

Impossible. The server can’t access a client’s GUI according to this little snippet:

(This article on the DevHub, if you’re curious.)

Hey, I’ve made quick repro place for you to see that it is actually possible.
PlayerGuiRepro.rbxl (32.7 KB)

And in official DevHub article there isn’t anything about not replicated across Roblox’s server/client boundary. PlayerGui (roblox.com)

Or you could secure your remote events like a normal person rather than taking the most obscure and janky routes possible.

While I agree with you I was referring to my previous quote:

That’s not a janky route though, that’s genuinly just contextually locating your remote. In this case, the average client has absolutely no reason to see that remote, so then they will not see it. Otherwise you’re just wasting memory

and @AlphaEpsilon290

Impossible. The server can’t access a client’s GUI according to this little snippet:

That’s correct, that’s why you have a Client script in there too, that’s why it may look like this:

image

Upon being parented to a player’s gui, both the server and the client script will run, commencing the admin. And because all of this info is isolated solely to the gui, and the gui does not exist to any other player, it is then perfectly secure.

No, I think this just means that any changes to the gui on the client do not replicated to the server.

Yes it is. Your relying not on your own code, but on Roblox’s replication, which could change at any time, or have some random undocumented feature.

An if statement works just as well, and requires much less thought.
Doing stuff like this is essentially over complicating a what would be simple task, that has much less areas it can go wrong.

but on Roblox’s replication, which could change at any time, or have some random undocumented feature.

That is absolutely fair, if it applied here. Which I don’t think it does, the current state of ServerScriptService and ServerStorage is that they are absolutely isolated to the server side, even back in the days of Filtering enabled being toggleable, you still couldn’t see server side things. So roblox will likely never change that core expected behavior enough to break it to any measurable degree.

An if statement works just as well, and requires much less thought.
Doing stuff like this is essentially over complicating a what would be simple task, that has much less areas it can go wrong.

I say the exact opposite, when you keep something on server side, you expect it to only be visible to the server, when you put it on the client, you expect it to be visible to the client and sometimes the server as well. That’s extremely simple, however you can complicate it by taking it out of the space that’s supposed to only be server-side, giving it to the client for no reason, and dealing with the implications of that. Is that not complication? And even then, although this relies on your own code, it’s still subject to the exact same implications you mentioned, maybe more so even, the way they handle UserId may change.

I dont want to make a seperate post just for this question, but can an exploiter fire a remote event to all clients? Or just to the server.

they can only fire remote events to the server. However, if you make a remote event fire all clients via code on the server inside the said remote then the exploiter can simply fire from the client that remote event which in turns fires the other ones

1 Like

UI management should not be done on the server. The path you are taking is bad practice. Just have the GUI in ReplicatedStorage or in StarterGui, and then validate incoming requests on the server.

You should not take certain paths that may seem quicker just because you can.