Is it possible for exploiters to change StringValues?

I have a shop where when an item is equipped, a StringValue inside of the player called EquippedSword’s value gets changed to the name of the sword. When a round starts, the game checks the value of EquippedSword and gives the player the sword that corresponds to the value.

My question is, is it possible for exploiters to change the value of this StringValue manually with exploits so the game gives them a sword? Let me know if this is in the wrong category too, I was unsure of where to put it.

Thanks alot. :happy3:

They can edit the value, yes, but locally. Does your game check the value on the server side? If not, you should change that behavior.

1 Like

The game checks the value on the server side, yes. So I should generally have nothing to worry about then?

General rule:

  • Anything done on the client stays on the client. Never trust anything the client sends to the server.

Like @SomeFedoraGuy said, if they change the value, it will only change for them. As long as you are not reading their locally edited value (through RemoteEvent or RemoteFunction) you have nothing to worry about. Change the value on the server, and read it on the server. They can do whatever they want to it on their client, but it won’t replicate.

3 Likes

Since the server does the value checking, then no. I suppose the server directly reads using .Value, so if you don’t have any Remotes sending the value data then you’re good.

1 Like

Alright. When someone equips a sword it’s actually handled through a RemoteEvent since the purchase script is client sided, however I watched some videos and exploit-proofed it as much as I could so exploiters can’t change it.

But yes, the server directly checks the value of EquippedSword and doesn’t use any RemoteEvent’s or RemoteFunction’s. Thanks alot!

Make sure to secure your events as securely as possible.

This includes things like:

  • Typechecking any paramaters passed
  • Checking the player actually has enough money
  • Checking the player does not own the sword
  • Checking the name passed is that of a legitimate sword
  • Debouncing remotes for individual players so they cannot spam them (DoS/DDoS attack)

This is the RemoteEvent listener script, the RemoteEvent gets fired whenever a player clicks buy. I watched some tutorials and videos - not the best scripter, is this secure enough?

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local swordPurchasedEvent = ReplicatedStorage.RemoteFunctions.SwordPurchasedEvent

local swordPrices = {
	["Spartan Sword"] = 500,
	["Steampunk Sword"] = 1000,
	["Skelly Slayer"] = 1500,
	["Signifier Sword"] = 2000,
	["Darkheart"] = 2750,
	["Overseer Wrath"] = 5000,
	["Serpent Sword"] = 6000,
	["Light Blade"] = 7500,
	["Plated Sword"] = 9250,
	["Uppercut Sword"] = 11000,
	["Evil Knight"] = 13450,
	["Arabian Knight"] = 16000,
	["Spec Elipson"] = 19000,
	["Interstellar Sword"] = 23500,
	["RGB Sword"] = 31000,
	["Violet Sword"] = 40000,
	["Gilded Sword"] = 52500,
	["Omi Odachi Sword"] = 65000,
	["Orc Blade"] = 80000,
	["Sorcus Sword"] = 97500,
	["Sword Of Darkness"] = 111000,
	["Amethyst Rock Sword"] = 125000,
	["Blood Sword"] = 200000,
	["Molten Scythe"] = 250000,
}

swordPurchasedEvent.OnServerEvent:Connect(function(player, sword)
	local weapon = ReplicatedStorage.Swords:FindFirstChild(sword)
	if weapon then
		if player.leaderstats.Gems.Value >= swordPrices[sword] then
			player.leaderstats.Gems.Value -=swordPrices
		end
	end
end)

@SomeFedoraGuy You can check this out too if you’d like, I appreciate it. :happy3:

Is “sword” a string? You should probably typecheck that because exploiters can pass the wrong data type and force an error.

You can use type() to get the main classes, and typeof() to include Roblox classes. I’ll just use typeof().

swordPurchaseEvent.OnServerEvent:Connect(function(player, sword)
    if typeof(sword) ~= "string" or not swordPrices[sword] then return nil end
    --rest of code goes here
end)

We don’t need to worry about the wrong data type for the second statement because if the first one returns false Lua doesn’t even bother checking the second statement.

Thanks alot, I’ll be sure to add that in. I also had another question regarding this.

When the player confirms the purchase, that’s when the RemoteEvent is fired. They confirm the purchase by pressing a button, the client-sided script checks the players leaderstats to see if they have enough and then fires the remote, if they don’t have enough it prints “Not enough”.

However, wouldn’t an exploiter just be able to change their leaderstats locally and then be able to purchase the sword? Do you have any recommendations on how to check the players server-sided leaderstats instead of client-sided which they can change, would I need to use another RemoteEvent? Thanks. A segment of the purchase script is below.

local function buyItem()
	clickSound:Play()
	script.Parent.Parent.Parent.Visible = false

	local confirmationUI = player.PlayerGui["User Interface"].UserUI.SwordConfirmationUI
	confirmationUI.SwordName.Text = script.Parent.Name

	if confirmationUI then
		confirmationUI.Visible = true
		confirmationUI.GemsCost.Text = gemCost
		confirmationUI.SwordImage.Image = script.Parent.ImageLabel.Image
		local yesButton = confirmationUI:FindFirstChild("YesButton")
		if yesButton then
			yesButton.MouseButton1Click:Connect(function()
			if player.leaderstats.Gems.Value >= gemCost then
				swordPurchasedEvent:FireServer(gemCost, sword)
				confirmationUI.Visible = false
				equipButton.Visible = true
				TweenUI()
				game.ReplicatedStorage.GameSounds.PurchaseSound:Play()
			else
				print("Not enough gems!")
			end
		end)
		end

You’re already checking the value on the server, so that’s fine! It’s the same with the StringValue. Any changes on the client stay on the client. As long as you aren’t reading the value from the client for the whole purchase, and checking that server-side, you should be fine.

I actually tried it out for myself, it seems like if the player has 0 gems (client or server) the purchase doesn’t go through, however I changed my Gems value on the client side and the script seemed to make the purchase go through, even though I only had enough Gems on the client side. Maybe I made some mistake, let me know if I did.

However, I added a RemoteFunction that checks if the player has enough Gems on the server side before going through, otherwise it prints not enough gems. This is what I adjusted the script too and it seems to be working fine now and still prints “Not enough gems” even if the player changes their leaderstats on the client side and only goes through if they have enough on the server.

LocalScript

local function buyItem()
	clickSound:Play()
	script.Parent.Parent.Parent.Visible = false

	local confirmationUI = player.PlayerGui["User Interface"].UserUI.SwordConfirmationUI
	confirmationUI.SwordName.Text = script.Parent.Name

	if confirmationUI then
		confirmationUI.Visible = true
		confirmationUI.GemsCost.Text = gemCost
		confirmationUI.SwordImage.Image = script.Parent.ImageLabel.Image
		local yesButton = confirmationUI:FindFirstChild("YesButton")
		if yesButton then
			yesButton.MouseButton1Click:Connect(function()
				local checkGems = game.ReplicatedStorage.Functions.CheckGems:InvokeServer(gemCost)
				if checkGems then
				swordPurchasedEvent:FireServer(gemCost, sword)
				confirmationUI.Visible = false
				equipButton.Visible = true
				TweenUI()
				game.ReplicatedStorage.GameSounds.PurchaseSound:Play()
				else
					print("Not enough gems")
				end
			end)
		end

		local noButton = confirmationUI:FindFirstChild("NoButton")
		if noButton then
			noButton.MouseButton1Click:Connect(function()
				confirmationUI.Visible = false
				clickSound:Play()
				script.Parent.Parent.Parent.Visible = true
			end)
		end
	end
end

Server-Side Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local checkGemFunction = ReplicatedStorage.Functions.CheckGems

checkGemFunction.OnServerInvoke = function(player, gemCost)
		if player.leaderstats.Gems.Value >= gemCost then
			return true
		else
			return false
		end
	end

Let me know if this all looks good, thanks alot. :happy3:

Yeah, it looks good, but you shouldn’t need a RemoteFunction. Are you sure you have nothing else that might edit values sent from the client? Because if checking on the server doesn’t work, checking on the client is very exploitable. Do you still have the if statement on the server?

I’m actually running into another issue now. The script that deducts the gems actually thinks that the sword name is the price now for some reason.

When I try to purchase Spartan Sword in-game, I added some debug and the script says “No sword found called: 500”, which is the price of the Spartan Sword. Any idea why this might be happening? This is the script;

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local swordPurchasedEvent = ReplicatedStorage.Events.SwordPurchasedEvent

local swordPrices = {
	["Spartan Sword"] = 500,
	["Steampunk Sword"] = 1000,
	["Skelly Slayer"] = 1500,
	["Signifier Sword"] = 2000,
	["Darkheart"] = 2750,
	["Overseer Wrath"] = 5000,
	["Serpent Sword"] = 6000,
	["Light Blade"] = 7500,
	["Plated Sword"] = 9250,
	["Uppercut Sword"] = 11000,
	["Evil Knight"] = 13450,
	["Arabian Knight"] = 16000,
	["Spec Elipson"] = 19000,
	["Interstellar Sword"] = 23500,
	["RGB Sword"] = 31000,
	["Violet Sword"] = 40000,
	["Gilded Sword"] = 52500,
	["Omi Odachi Sword"] = 65000,
	["Orc Blade"] = 80000,
	["Sorcus Sword"] = 97500,
	["Sword Of Darkness"] = 111000,
	["Amethyst Rock Sword"] = 125000,
	["Blood Sword"] = 200000,
	["Molten Scythe"] = 250000,
}

swordPurchasedEvent.OnServerEvent:Connect(function(player, sword)
	local weapon = ReplicatedStorage.Swords:FindFirstChild(sword)
	if weapon then
		if player.leaderstats.Gems.Value >= swordPrices[sword] then
			player.leaderstats.Gems.Value -=swordPrices
		end
	end
end)

Adding onto what you said, do you mind elaborating a bit? What do you mean by do I still have if statements on the server? If by “on the server” you mean the listener script that deducts the gems, I’ve pasted that above for the other issue so you can take a look at that too. Thanks alot. :happy3:

You’re firing the cost as well, which you don’t need to do.

1 Like

Ah yes, forgot to change that.

I always thought that it would just ignore variables that are fired but not used in the listening script. Script works fine now. Thanks alot!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.