Hey there, I’m Drinkinix. I’ve been wondering for a while on how to make non-exploitable scripts, aka flawless ones. This means that exploiters will have a hard time exploiting your game. Where do I start?
Descriptive explanations would be very much appreciated!
Never, ever, ever trust the client
That’s the root of all security on Roblox, on any action check what the client sends, and make sure that it’s a valid request, and that the user is able to perform the action. Start by making your game FE compatible [don’t have a choice with that]. Use RemoteEvents/Functions. The rest kind of depends on your application.
Example:
When a remote event is fired, and it needs to start a course of action, I.e. spawning a pet, or something like that make sure the client has bought access to the said thing, then spawn it.
Sanity checks FTW.
No code is “non-exploitable”, there’s always a way for exploiters to mess with something. They always have the upper hand with higher context levels and custom implementations allowing them to do nearly anything, little of which you can protect yourself against excluding hacky methods which are not 100% foolproof or reliable.
To mitigate the number of exploiters and limit any exploiting to fairly harmless stuff, I suggest:
Verifying all client input to the server (where it makes sense) to ensure that it’s a legitimate action. If I shoot my gun and it’s going in an odd direction, through buildings and hitting a player directly in the head it probably isn’t a legitimate action. Many edge cases exist so make sure you factor all those in.
Give the client only needed control over server actions, only allowing them to do the bare minimum. You wouldn’t give the client arbitrary code execution or the ability to change properties/create instances, so don’t give them the ability to. Ensure all potentially risky actions occur on the server with as much verification and as little client input as needed.
Don’t create over the top client side checks or rely on the client for exploit prevention. Any and all checks on the client can be circumvented, you shouldn’t spend precious development time creating fancy systems to obfuscate code or similar. While I do suggest implementing basic checks to decrease potential useless server traffic as possible (and just for general ease and client response time), you shouldn’t rely on them.
Of course, as with anything, this won’t apply to 100% of cases. There are an infinite number of possible game ideas and scenarios within them which not even the best tutorial or reply can account for all of. With this knowledge, you should go through your code and identify potential points of attack. You, and only you, can truly protect your code and game the best. The only things we can do is offer advice and our past experience in an attempt to educate you and other people.
This is trusting the client to send you the correct name of the correct TextButton, rather than some spoofed string.
What you’d want to do instead is send a reference to the TextButton to the server, and have the server collect the name
Not every single piece of user input needs to be verified. The name of a TextButton should be the least of your concerns.
Client side changes to the UI elements will not replicate to the server. If you typed in a TextBox, change a TextButton name etc. on the client, that isn’t going to replicate. Thus, it’ll return whatever the original value of it is.
Little bits of text like this don’t need to be verified in most cases, but should it need to be just send the raw input from the client (potential client side checks here are valid) and verify it on the server, don’t send references or anything like that.
This isn’t “every single piece”, this is the price of an item in a shop, which is probably the most used example for Roblox security. If the TextBox’s name is being compared to something on the server:
then it is very important that we verify this user input.
Think about how the client would send this information. In the original example:
-- CLIENT
--We are parsing the name of the TextButton
game:GetService("ReplicatedStorage").BuyItem:FireServer(TextButton.Name)
-- SERVER
--We are comparing this value
game:GetService("ReplicatedStorage").BuyItem.OnServerEvent:Connect(function(value)
if value < player.Cash then
sellItem()
end
end)
a malicious user could simply replace the TextButton.Name with whatever they wanted to, causing unintended behaviour on the server.
The way to fix this is to send a reference to the object and then access the information from the server - all of this ignoring the fact that storing values in instance names is bad practice.
Client side checks are never valid, this is Security 101 here
You put a simple sanity check, if it’s a string and if it’s in the dictionary, continue.
If it’s not, don’t. It’s as simple as that. There’s no reason to do this unless you’re just overly paranoid for whatever reason. There’s also no point in “sending a replaced version of TextButton.Name”, when, like I said, there would be sanity checks, and you’re only harming yourself if something did go wrong. What exploiter would want to do that?
To be honest, yeah, this is a little confusing. What do you mean by “the price of TextButton”? TextButton is an instance, it doesn’t have a “price” property. You then talk about sending the Name of the TextButton, which is why I made the assumption I did.
Anyway, what I’m trying to get at is that you’re not following security conventions in your designs, and you’re encouraging some potentially problematic coding practices in your example.
If you’d really like me to explain why what you’re doing can be broken, feel free to go into more detail about how you would implement your system.
Also:
What are you storing in your dictionary - prices or item names? You’re not being clear about it at all
Say they click a text_button, that we named “Sword”.
On the server, we’d check if “Sword” (the parameter we passed, aka TextButton.Name) was in the dictionary, and then we’d check if the player’s money was >= the price of “Sword”
I think @xRuinedSeven meant that you would index a dictionary for the price, not get the price itself from the name:
local Prices = {
Item1 = 5,
Item2 = 20,
Item3 = 30
}
buyItem.OnServerEvent:Connect(function(player, itemName)
local price = Prices[itemName]
if price and player.Money >= price then
-- buy item from name
end
end)
Your example (for whatever reason) is attempting to pass off a TextButton’s Name value as the price/cost for an item, this would never be the case. You claim this as “Security 101” despite trusting the client with providing a value for the sale of an item (no matter whether the server “gets a reference” to it or not)? That is not how it works.
If a user wanted to sell an item, you’d send the name of that item to the server and check the prices on the server, you would not send a reference to a client UI element to the server to do so. There is absolutely no harm in the client sending some modified value if the server checks it, ensures they own said item and any associated values are correct.
You’re skimming over information and not taking into account the full meaning of my post. “Potential client side checks” refer to the mere skimming over data ensuring it’s (supposedly) correct on the client before being sent to the server, overall to improve client response time and less network traffic (albeit such an amount would be negligible)
Apologies if I worded my reply incorrectly, but it seems to make sense and is legible.
Yes this would work, but my point was that, in general, it is preferable to have input that doesn’t need to be checked than it is to check the input. This is because in more complicated systems checks become more and more complex, and there are more potential points of failure.
Whilst the example you gave is perfectly secure, it should not be entirely extrapolated from because of this.