How hackers can exploit your servers and what to do about it

Notice!

This guide is now quite out-of-date and in need of a rewrite. While much of the sentiment and the generalised statements made in the post remain true, most of the methods discussed are no longer relevant. I don’t have the time as of now to properly update this thread but I have removed some of the sections which have lead to some frankly incorrect assumptions on how much control is available over the client-side of the FiveM scripting environment. The original content of the post will now follow:

Hello again!

Following on from my last post about new frameworks, I thought it may also be a good idea to share my knowledge with the community on how hackers exploit servers and how to best protect your server from them.

First though, it needs to be pointed out that hackers, like any other person with malicious intent, are always going to find new exploits and ways around defences that have been put in place to stop them. FiveM’s developers work tirelessly to not only ban everyone who cheats but keep innocent players from getting caught in the crossfire. However, there are always people who manage to slip through the cracks. This is where you come in! Your server’s security should be of utmost importance. Even when you live in a gated community, you still lock the front door at night so why would you leave your server wide open for people to exploit?

In order to understand how you can patch exploits on your server, you need to know what hackers are even exploiting in the first place.

What currently hackers exploit

Almost every hack available for FiveM at the time of writing focus around running arbitrary Lua code on the client. In an ideal world, this wouldn’t matter however, we do not live in an ideal world. Being able to run any client-side Lua code allows access to game functions or natives such as CreateVehicle which spawns vehicles or AddExplosion which creates explosions.

However, what gets abused is the most is the TriggerServerEvent function. TriggerServerEvent is used to send signals to the server, asking it to run Lua code defined in the server script files. On paper, seems completely fine and necessary for communication between the client and server but sadly, the majority of resources do not take into account clients being able to trigger events arbitrarily. Take for example the esx_pizza resource.

In that resource, there is a server event named esx_pizza:pay. Going by the name, when a client triggers that event, the server figures out how much money the player who triggered it should get depending on what work they did, and then give it to them, right? Wrong.

Here is the entire server-side code for that resource:

ESX = nil
TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)

RegisterServerEvent('esx_pizza:pay')
AddEventHandler('esx_pizza:pay', function(amount)
	local _source = source
	local xPlayer = ESX.GetPlayerFromId(_source)
	xPlayer.addMoney(tonumber(amount))
end)

I understand not all of you can read Lua code so I’ll try to give a simple explanation. When the job has been completed, the client triggers the esx_pizza:pay event (using TriggerServerEvent) along with an amount value. amount is equal to the amount of money the client should get. When the server receives the event, the server runs the code within AddEventHandler. That code gets the amount value sent to it by the client and just adds amount to the client’s total money.

See where the issue is? There are no checks for if the player completed the job recently, no validation of the amount given. The client controls how much the server gives to them, not the other way around. This means a hacker can just execute the code that triggers the esx_pizza:pay event with some obscene amount value and give themselves millions of dollars, whenever they feel like it!

So, what can we do?

Patching resources

Well bad news is there is no easy one-size-fits all fix to resource vulnerabilities. You (or ideally the individual resource makers) will need go find exploitable events in the server-side code and rewrite that code to remove the exploit.

As an example of what to look for, let’s go back to esx_pizza. The first thing to do is look at the client-side code and find exactly where esx_pizza:pay is called. Using my code editor’s find function, it shows me that esx_pizza:pay gets called in the following function:

Payout Function (original)
function donnerlapaye()
	ped = GetPlayerPed(-1)
	vehicle = GetVehiclePedIsIn(ped, false)
	vievehicule = GetVehicleEngineHealth(vehicle)
	calculargentretire = round(viemaxvehicule-vievehicule)
	
	if calculargentretire <= 0 then
		argentretire = 0
	else
		argentretire = calculargentretire
	end

    ESX.Game.DeleteVehicle(vehicle)

	local amount = livraisonTotalPaye-argentretire
	
	if vievehicule >= 1 then
		if livraisonTotalPaye == 0 then
			ESX.ShowNotification(_U('not_delivery'))
			ESX.ShowNotification(_U('pay_repair'))
			ESX.ShowNotification(_U('repair_minus')..argentretire)
			TriggerServerEvent("esx_pizza:pay", amount)
			livraisonTotalPaye = 0
		else
			if argentretire <= 0 then
				ESX.ShowNotification(_U('shipments_plus')..livraisonTotalPaye)
				TriggerServerEvent("esx_pizza:pay", amount)
				livraisonTotalPaye = 0
			else
				ESX.ShowNotification(_U('shipments_plus')..livraisonTotalPaye)
				ESX.ShowNotification(_U('repair_minus')..argentretire)
					TriggerServerEvent("esx_pizza:pay", amount)
				livraisonTotalPaye = 0
			end
		end
	else
		if livraisonTotalPaye ~= 0 and amount <= 0 then
			ESX.ShowNotification(_U('truck_state'))
			livraisonTotalPaye = 0
		else
			if argentretire <= 0 then
				ESX.ShowNotification(_U('shipments_plus')..livraisonTotalPaye)
					TriggerServerEvent("esx_pizza:pay", amount)
				livraisonTotalPaye = 0
			else
				ESX.ShowNotification(_U('shipments_plus')..livraisonTotalPaye)
				ESX.ShowNotification(_U('repair_minus')..argentretire)
				TriggerServerEvent("esx_pizza:pay", amount)
				livraisonTotalPaye = 0
			end
		end
	end
end

This code is a bit more complex and is also in french so I translated it and added some comments so it’s easier to see what’s going on:

Payout Function
function donnerlapaye()
	local ped = PlayerPedId() -- This is the player's ped
	local jobVehicle = GetVehiclePedIsIn(ped, false) -- This is the player's vehicle that they are currently sitting in
	local jobVehicleHealth = GetVehicleEngineHealth(jobVehicle) -- This is that vehicles health
	local jobVehicleDamage = round(jobVehicleMaxHealth-jobVehicleHealth) -- This is the job vehicles damage
	
	if jobVehicleDamage <= 0 then
		jobVehicleDamage = 0
	end

    ESX.Game.DeleteVehicle(jobVehicle)

	local amount = jobCurrentPayout-jobVehicleDamage
	
	if jobVehicle >= 1 then
		if jobCurrentPayout == 0 then
			ESX.ShowNotification(_U('not_delivery'))
			ESX.ShowNotification(_U('pay_repair'))
			ESX.ShowNotification(_U('repair_minus')..jobVehicleDamage)
			TriggerServerEvent("esx_pizza:pay", amount)
			jobCurrentPayout = 0
		else
			if jobVehicleDamage <= 0 then
				ESX.ShowNotification(_U('shipments_plus')..jobCurrentPayout)
				TriggerServerEvent("esx_pizza:pay", amount)
				jobCurrentPayout = 0
			else
				ESX.ShowNotification(_U('shipments_plus')..jobCurrentPayout)
				ESX.ShowNotification(_U('repair_minus')..jobVehicleDamage)
				TriggerServerEvent("esx_pizza:pay", amount)
				jobCurrentPayout = 0
			end
		end
	else
		if jobCurrentPayout ~= 0 and amount <= 0 then
			ESX.ShowNotification(_U('truck_state'))
			jobCurrentPayout = 0
		else
			if jobVehicleDamage <= 0 then
				ESX.ShowNotification(_U('shipments_plus')..jobCurrentPayout)
				TriggerServerEvent("esx_pizza:pay", amount)
				jobCurrentPayout = 0
			else
				ESX.ShowNotification(_U('shipments_plus')..jobCurrentPayout)
				ESX.ShowNotification(_U('repair_minus')..jobVehicleDamage)
				TriggerServerEvent("esx_pizza:pay", amount)
				jobCurrentPayout = 0
			end
		end
	end
end

So, to put that code simply, it first gets the job vehicle’s health, calculates its damage, takes that from the potential payout and then tells the server to give that money to the player. The massive problem is that the entire script is client sided and if the client has to give the server any of the payment values, it is exploitable! As I showed before, the server code is only that vulnerable function and nothing else. This means this script would have to be rewritten so that the values are not calculated on the client but rather on the server. While this adds additional complexity, it also makes it 1000x harder to exploit.

Thanks to @TheIndra, I now have an example to give of how to do it right! secure-resource-examples/esx_potjob at master · TheIndra55/secure-resource-examples · GitHub is a great example however, this one resource does not cover everything (and how could it!) so when rewriting scripts and especially writing new ones, you should always remember, never trust the client.

Validating client actions

So, now I’ll assume you’ve patched out every exploitable event in your entire server. That’s great! Now you can start being on the offensive and make sure clients that join your server aren’t allowed to do things that you haven’t allowed to happen. What I mean by that is checking that client’s aren’t doing things that are impossible.

Event Honeypots

The most effective way to do this is through an event honeypot. The concept is to setup a bunch of event handlers for events that are commonly exploited by hackers (and their hack menus) but don’t actually exist on your own server. Somebody ran es_admin:banPlayer and you don’t even run an ESX server? Ban. Somebody else tried to jail a player without being a police officer? Stop the player from being jailed. The possibilities are endless!

Now you could set up an event handler that responds all events and ban when an event is run that isn’t handled by any of your scripts. However, this is currently a bad idea as FiveM updates add new net events all the time. One update is all it takes to automatically ban anyone who connects to your server until you add that event to your whitelist. The good news is though, this will potentially be a viable option with event documentation in the works and providing that all events are documented going forward, one could set up a system which scrapes the event documentation for new events and automatically whitelists them.

I hope this helps everyone!

46 Likes

Here is a modified version of basic esx payout scripts that has been modified to counteract these specific attacks. You should calculate the maximum payout for the job you have in order to work this out for yourself. Also is good to log every payout in case user decides to go under max payout and hide from being caught/banned. Watch logs of it if it happens and it’s obvious the person getting 10k every 2 seconds is cheating.

RegisterServerEvent('pop_pizzero:propina')
AddEventHandler('pop_pizzero:propina',function(propina)
	TriggerEvent('es:getPlayerFromId',source, function(user)


	local _source = source
	local xPlayer = ESX.GetPlayerFromId(_source)
	local payamount = math.ceil(propina)

	if amount <= 10000 and not nil then
		user.addMoney((propina))
		TriggerClientEvent('esx:showNotification', source, '~s~Received~g~ '..payamount..' ~s~from this stop~s~!')
		TriggerEvent('DiscordBot:ToDiscord', 'job', 'Jobs', xPlayer.identifier ..' '..xPlayer.getName() ..' received ' ..payamount .. 'from PIZZA', 'IMAGE_URL', true)
	else
		TriggerEvent('DiscordBot:ToDiscord', 'cheat', 'CLRP ANTI CHEAT', xPlayer.identifier ..' '..xPlayer.getName() ..' cheated and received ' ..payamount .. 'from Garbage Crew @everyone', 'IMAGE_URL', true)
-- put your banning trigger here
-- Didn't include autoban because it's setup to require custom DB Events etc
	end

	end)
end)
4 Likes

This way you would still be just taking the user money and blindlessly giving it

I would more do it like this

client send pizzajob:start when it starts the job, server stores that player is on job

Then when the user hits something or damages it’s vehicle it’s send to the server and server will keep that value

When a pizza is delivered it will also send it to server and server will store amount of pizzas delivered in that job session

Then when player is done he triggers pizzajob:imdone and server calculates all money user should check, then you could even do some time checks so it’s a legit time etc etc

3 Likes

@Chained Thank you for sharing your solution and @TheIndra thank you for you critical analysis! This communication within our server owner community is what I’d really like to see more of!

1 Like

If you are running onesync you could probably even do more checks using any serversides native, the server shouldn’t blindlessely accept an amount

This is the same with some things i see happening with jail resources, people join a server and jail everyone. that just shouldn’t be possible you should have an check on the server for example

AddEventHandler('jail:jail',function(target, time)
    -- dont continue if player trying to jail someone isn't a cop
    if xplayer.getbla(source).job ~= "police" then return end

    -- do jail stuff
end)

I have created a simple basic job which kinda implements the kinda system i mentioned above

https://github.com/TheIndra55/secure-resource-examples/tree/master/esx_potjob

Ofcourse an attacker could still execute the events in right order, but he could never make more money than a job should make and if you have locations far away from eachother you can make the time between each delivery much higher which makes it almost useless for an attacker

If you are running onesync you can even do more serverside checks, you could even check the location and other stuff too

2 Likes

Thank you! This will act as a good example of how to do it properly.

Moved to #server-development:server-discussion as what feels like a million people are requesting access to this topic.

3 Likes

I have modified the example job to now also do a position check (if onesync) so if someone tries to call esx_potjob:delivered from a totally different position than the delivery location is it cancels and logs it

1 Like

I’ve updated the topic with some new information, enjoy all!

thank you you saved my fivem server

3 Likes

Now that is beautiful.

1 Like

Another thing that server owners should know is Never give weapons from client side, like for example LegacyFuel:

People can modify the weapon hashes with CE and get an assaultrifle

This code does not work, since GetNumResource() is a global and needs to be replaced with
GetNumResources()-1

Have a nice day!

1 Like

Ah, got the wrong native name! Thanks for picking up on that :slight_smile:

1 Like

Hello, I have added the last code you’ve posted with checking of resources in client and server side but it doesn’t work, I have a friend that tested it on my test server and it didn’t kick him or ban him or anything like that after he injected a menu and waited more than 15 seconds… please could you help ?

It’s most likely that whatever menu was used (and keep in mind, don’t use hacks to test your anticheat as you run the risks of being banned!) doesn’t trigger any of the sample code provided. It’s only there as an example for how mitigations could be accomplished, not a put it in your server and you’re protected piece of code.

It may be that hacks of this time have worked around those methods of detection and that is just how it will have to be until some new method of detection is found. The best method of preventing people from messing with your server is to assume they can execute any code they wish at any time and designing around that possibility rather than assuming it will be impossible.

Wait, how are we supposed to audit the security of our server without using cheats?
Isn’t there a way for developers to test cheats in their own server, or at least locally in their LAN?

Most cheats are just Lua executors. If you have access to the Lua menu file, just make an empty resource and put it as a client_script in the resource manifest.

1 Like

Will this prevent a cheater from actually using the actual menu? Can’t they just rename it?

Also I’m more worried about other kinds of hackers; I read the unknown cheats forums a lot, just to stay updated on what they’re doing. They go as far as loading custom drivers. Fortunately only a small percentage of cheaters are able and willing to compile and load those drivers.

Still I’d like FiveM to allow us to write, test and debug our own solutions.