Recently, me and my friend released a UGC limited, hopefully to drive people to play our game.
Much to our dismay, people changed the datastores (or maybe just the values that are saved) and got the UGC instantaneously. Now our UGC, which should have taken a while to sell out, has been emptied of its stock in just a few minutes due to exploiters.
There should be no way the client can communicate data to the server. There is one client to server remote event in which the client only tells the server that a user clicked on a game pass.
Does anyone know how this happens / how to prevent it, thankfully only one UGC was released at the time, but this is still an issue that me and the dev team of the game need to address, and change to hopefully make smoother UGC releases.
First, you should have shown a code snippet which handles the UGC receival to help everyone better understand the issue, however possible causes are:
What could be the possible issue:
Possible backdoor in your game if you or one of your developers have used a free model, or one of your developers has inserted one deliberately.
Client having way too much trust, thus resulting in exploiting and giving the malicious users the ability to manipulate the server.
Not having sanity checks on the server side, for example with RemoteEvents if a script expects an integer but it receives a string it may cause to error and break the script or crash the server.
How to prevent these?
Check your game for any malicious code, suspicious require and alike functions in your scripts.
Never trust the client, if the server trusts the client then there is no way to detect malicious behavior from a user and the user, with knowledge can use it to their advantage to exploit/change your game’s behavior.
Implement sanity checks on server side scripts when expecting data from a client.
Thank you for the response! But (my fault) because i didn’t proved any code, I don’t know where to identify these problems. Here’s my thoughts at the current moment:
-The only backdoors I could imagine in this game would be caused by plugins. All of the devs only want success for the game, and our plugins are all legit.
-I don’t fully understand what trusting the client is, but I believe that I don’t do this anyways. The client does not store ANY data or share anything to the server that could allow them access to anything they shouldn’t be able to. The only thing the client sends to the server is whether they wish to buy a product
ReplicatedStorage.PassPurchase.OnServerEvent:Connect(function(player,pass)
local PassId = Passes[pass] --passes is a table that holds six passes, and the client sends a number from 1 to 6
MarketplaceService:PromptProductPurchase(player,PassId)
end)
-I don’t see where i would need sanity checks (once again a bit unfamiliar territory for me) because the client shouldn’t be able to manipulate data, it is all handled on the server.
Here is how the item is awarded to the player:
proximityPrompt.Triggered:Connect(function(player)
if OtherData[player.UserId][v.Name] then --basically checks if player already bought item
--OtherData is a table that is saved to a datastore. This table holds, for each player, what UGC's they have purchased (you unlock the ugc with in game currency)
MarketplaceService:PromptPurchase(player,v:GetAttribute("Id"))
elseif Scores[player.UserId] >= price then
--for each player, scores holds how much currency they have (server sided), this is saved to a datastore
Scores[player.UserId] -= price
OtherData[player.UserId][v.Name] = true --Basically sets it so the player has unlocked the item
ReplicatedStorage.ProxPromptUpdate:FireClient(player,OtherData[player.UserId])
--The point of this is to tell the client to make some graphical changes to show the player that the item is unlocked
MarketplaceService:PromptPurchase(player,v:GetAttribute("Id")) --Id is the UGC id
end
end)
If more context for the code is needed I will happily provide it, I really want this fixed for next time
Hey, my head hurts but I’ll try to help as much as I can, btw, is it not possible to revert the UGC/refund the lost UGC? I don’t know much about the whole UGC thing, anyway.
I don’t fully understand what trusting the client is, but I believe that I don’t do this anyways.
Trusting the client is letting the client (local scripts) do all the checking and verifying of data instead of using the server for that.
There is one client to server remote event in which the client only tells the server that a user clicked on a game pass.
Maybe this could have been the cause? Exploiters can send any data using RemoteEvents to the server and somehow bypass the purchase.
I don’t see where i would need sanity checks
Having sanity checks is crucial for securing your game as much as possible, but it will never be 100% impenetrable.
As for the code, I see no issues really… however, there used to be an exploit with MarketplaceService where exploiters could completely bypass the purchase and trick the server into thinking the purchase went through (when in reality it did not).
If they send something that isn’t a number from one to six, the game will just error. I don’t see how that could be a cause, but you never know.
What even differentiates a sanity check from what I’m doing, I don’t really understand these and it seems pretty crucial
How did this even exist. I will look more into this later, because it definitely seems like the cause.
Thanks again for the responses, it makes a lot more sense now, but still a bit confusing.
What looked like what was happening is people just make their money go up, which that MarketplaceService exploit seems like exactly what was happening, because the passes give you money, they could have totally been spoofing them.
Glad I could help, I wish you best of luck in development and in the future, do more research about things you plan on adding in your game to be sure that you’re doing it right, it isn’t deprecated or exploitable.
What even differentiates a sanity check from what I’m doing, I don’t really understand these and it seems pretty crucial
I’m pretty sure if a script errors it will stop functioning, so adding checks and then if incorrect data is sent you can write code that will handle the incorrect data instead of letting the script error out.