Is firing a remote event from a module to the server exploitable?

Hello everyone. I have a quick question regarding modules

Lets say I have a data table here stored in my module script:

local module = {}

--What the player's data table would look like
local data = {
    roster = {
        offense = {},
        defense = {}
    },
    joined = true,
    stats = {
        wins = 0,
        losses = 0
    },
}

function module.sendData() 
    remoteEvent:FireServer(data) -- exploitable?
end

return module

I think the code block pretty much expresses my concern and the title is self explanatory, but if you didn’t understand, can hackers alter the data that goes through that remote event?

1 Like

Yes, that’s why we have the saying of “don’t trust the client”. You shouldn’t assume that 100% of data coming to the server through a RemoteEvent is untampered. The validity of data from a RemoteEvent is unrelated to your use of ModuleScripts.

In the first place, the client should not be authoritative of data, so this is already an implementation with a giant hole in it.

1 Like

Yes, this is exploitable because any exploiter can just require the script and use any functions/data inside. What’s stopping you from purely handling this on the server?

Wouldn’t handling everything on the server take a lot of resources and effect game performance? Also, is all the code in a local script accessible or just the data being passed on?

It would take less resources than continually sending data through a remote event. Security would also be guaranteed.

All the code.

Thanks for your reply, but I have a couple of questions:

would security be guaranteed when only sending data by when a player leaves?

Can exploiters change the code inside the local script, or is it only viewable?

How would you fire a RemoteEvent after a player leaves? The only sure way of security is by doing it purely on the server if possible.

They can change code.

Sooooo, there’s no safe way to handle user input?

Also, if I have heard correctly, sending data from server to client is entirely okay, am I correct?

Yeah, because it can all be changed. You shouldn’t be accepting user input for this anyways, as it looks like you could handle this purely server-side.

If you need user input, use :FireServer, but make sure you are able to control the validity of data coming through.

Yes.

Oh, this is for color choosing, not the game itself

Shouldn’t be an exploiting issue if it’s just Color3s coming in from :FireServer right? It’s not really important towards the game.

Once the data is received to the client it cannot be altered?

local players = game:GetService("Players")
local DSS = game:GetService("DataStoreService")

local DS = DSS:GetDataStore("ds")

local controllerMod = require(game.ReplicatedStorage.modules.teamManager)
local utilities = require(game.ReplicatedStorage.modules.utilities)

local baseModels = game.ReplicatedStorage.basePlayerModels
local baseHairs = game.ReplicatedStorage.baseHairs


local CleetColors = {"Blue", "Green", "Yellow", "Black", "White", "Pink"}
local offensive = {"QB", "RB", "WR","OL", "TE"}
local defensive = {"SS", "DL", "CB", "FS", "MLB", "ROLB", "LOLB"}

local firstNames = {"Calvin", "Eoin", "Luis", "Brendan", "Alessandro", "Prince", "Abdul", "Abbas", "Olly", "Albert", "Adam", "Ridwan", "Damon", "Corey", "DeAndre", "Aiden", "Elijah", "Marc", "Hayden", "Donavin"}
local lastNames = {"Evans", "Hayes", "Hill", "Taylor", "Bennett", "Washington", "Wallace", "O'Brien", "Rodriguez", "Davis", "Young", "Ward", "Reynolds", "Collins", "Martin", "Robinson", "Mithchell", "West", "Smith", "Brown"}

local importance = {
	["QB"] = 80,
	["RB"] = 40,
	["WR"] = 60,
	["OL"] = 35,
	["SS"] = 45,
	["DL"] = 40,
	["CB"] = 45,
	["FS"] = 45,
	["MLB"] = 40,
	["ROLB"] = 40,
	["LOLB"] = 40,
	["TE"] = 20
}

local statsNeeded = {
	["QB"] = {
		"Speed", "TAS", "TAM", "TAL", "Break Sack", "Throw Power", "Injury"
	},
	["RB"] = {
		"Speed", "Break Tackle", "Catching", "Injury", "Range"
	},
	["WR"] = {
		"Speed", "Break Tackle", "Catching", "Injury", "Range"
	},
	["OL"] = {
		"Speed", "Blocking", "Injury"
	},
	["SS"] = {
		"Break Pass", "Speed", "Range", "Catching", "Injury", "Tackling"
	},
	["CB"] = {
		"Break Pass", "Speed", "Range", "Catching", "Injury", "Tackling"
	},
	["FS"] = {
		"Break Pass", "Speed", "Range", "Catching", "Injury", "Tackling"
	},
	["DL"] = {
		"Speed", "Rush", "Injury", "Tackling"
	},
	["MLB"] = {
		"Break Pass", "Speed", "Range", "Catching", "Injury", "Tackling"
	},
	["LOLB"] = {
		"Break Pass", "Speed", "Range", "Catching", "Injury", "Tackling"
	},
	["ROLB"] = {
		"Break Pass", "Speed", "Range", "Catching", "Injury", "Tackling"
	},
	["TE"] = {
		"Speed", "Range", "Catching", "Injury", "Break Tackle"
	}
}

local maxStat = {
	["QB"] = {
		["Speed"] = 92
	},
	["OL"] = {
		["Speed"] = 70
	},
	["DL"] = {
		["Speed"] = 70
	},
	["RB"] = {
		["Range"] = 87,
		["Catching"] = 88,
	},
	["MLB"] = {
		["Speed"] = 88
	},
	["ROLB"] = {
		["Speed"] = 88
	},
	["LOLB"] = {
		["Speed"] = 88
	},
	["TE"] = {
		["Speed"] = 88,
	},
}

local minStat = {
	["RB"] = {
		["Speed"] = 83,
	},
	["WR"] = {
		["Speed"] = 80,
		["Catching"] = 70,
	},
	["SS"] = {
		["Speed"] = 80,
		["Catching"] = 70,
		["Break Pass"] = 70
	},
	["FS"] = {
		["Speed"] = 80,
		["Catching"] = 70,
		["Break Pass"] = 70
	},
	["CB"] = {
		["Speed"] = 80,
		["Catching"] = 70,
		["Break Pass"] = 70
	},
	["TE"] = {
		["Break Tackle"] = 70,
		["Catching"] = 80,
		["Speed"] = 75,
		["Range"] = 70
	}
}

function generateTeam()
	local team = {}
	local teamToIterate = {}
	local availablePositions = {"QB", "RB", "WR", "SS", "TE", "OL", "DL", "CB", "FS", "MLB", "ROLB", "LOLB"}
	local available = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}
	
	for i = 0, 25, 1 do
		local basePlrModels = baseModels:GetChildren()
		local hairBases = baseHairs:GetChildren()
		
		local baseNum = utilities.random(1, #basePlrModels)
		local SkinBaseModel = baseModels:FindFirstChild("base_"..baseNum)
	
		if not SkinBaseModel then return end
	
		local hairBaseNum = math.random(1, #hairBases)
		local charHair = baseHairs:FindFirstChild("hair_"..hairBaseNum)
		
		if not charHair then return end
		
		local player = utilities.createTable()
		player.cleetColor = CleetColors[utilities.random(1, #CleetColors)]
		player.baseModel = SkinBaseModel
		player.hair = charHair
		
		local randomPosNum = utilities.random(1, #availablePositions)
		player.position = availablePositions[randomPosNum]
		available[randomPosNum] -= 1
		
		local first_name_Num = utilities.random(1, #firstNames)
		local firstName = firstNames[first_name_Num]
		
		local lastName_Num = utilities.random(1, #lastNames)
		local lastName = lastNames[lastName_Num]
		
		player.name = firstName.." "..lastName
		
		if available[randomPosNum] == 0 then
			table.remove(availablePositions, randomPosNum)
		end
		
		player.overalls = {}
		local len
		
		for i, v in pairs(statsNeeded[player.position]) do
			if v == "Injury" then
				player.overalls[v] = utilities.random(85, 99)
			else
				local min
				local max
				if minStat[player.position] and minStat[player.position][v] then min = minStat[player.position][v] else min = 65 end
				if maxStat[player.position] and maxStat[player.position][v] then max = maxStat[player.position][v] else max = 99 end
				
				player.overalls[v] = utilities.random(min, max)
			end
			len = i
		end
		
		player.totalOverall = math.round(utilities.calculateTable(player.overalls)/len)
		player.importance = importance[player.position]
		
		player.morale = utilities.random(50, 100)
		
		player.contract = {}
		player.contract.length = utilities.random(1, 5)
		player.contract.salary = utilities.random(10000000 + (10000000 * player.totalOverall/100) + (10000000 * player.importance/100) - 1000000 * player.morale/10 , 30000000 + (10000000 * player.totalOverall/100) + (10000000 * player.importance/100) - 1000000 * player.morale/10)
		player.contract.total = player.contract.length * player.contract.salary
		
		table.insert(teamToIterate, player)
	end
	
	team.offense = {}
	team.defense = {}
	
	for i, v in pairs(teamToIterate) do
		if table.find(offensive, v.position) then
			table.insert(team.offense, v)
		elseif table.find(defensive, v.position) then
			table.insert(team.defense, v)
		end
	end
	
	warn("A team has just been generated!")
	return team
end

players.PlayerAdded:Connect(function(plr)
	local team = {}
	
	local data = DS:GetAsync(plr.UserId.."-stats")
	if not data then
		local roster = generateTeam()
		team.roster = roster
		game.ReplicatedStorage.events.chooseColors:InvokeClient(plr)
		local primary, secondary = game.ReplicatedStorage.events.chooseColors:InvokeClient(plr)
		
		team.colors = {}
		team.colors.primary = primary
		team.colors.secondary = secondary
	else
		if data.joined == true then
			team = data.team
		else
			local roster = generateTeam()
			team.roster = team
			local primary, secondary = game.ReplicatedStorage.events.chooseColors:InvokeClient(plr)
			
			team.colors = {}
			team.colors.primary = primary
			team.colors.secondary = secondary
		end
	end
	
	print(team)
end)

This won’t cost as much resources as continually sending data back and forth?

Ohhhhhhh, if it’s for color choosing then yeah, use RemoteEvents. Make sure to validate that a Color3 is coming through on the server though.

Ok, thanks a lot! character limit

1 Like

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