[Release][DEV] Server Event Security Tokens - Anticheat

salty_tokenizer
Add security tokens to FiveM server events that are accessible from the client in order to prevent against Lua injections (and similar cheats).

Features

  • A unique security token is generated for each resource each time the server is started. If the token is figured out by a cheater somehow, a restart will nullify their findings.
  • Tokens can only be requested by the client once. This prevents a cheater from attempting to retrieve the token through a command.
  • Players that trigger a server event without a valid security token are kicked from the game.

Installation

  • A unique security token is generated for each resource on each server restart.
  • Tokens are sent through listeners that are obfuscated per client and unique every time the player joins.
  • Tokens can only be requested by the client once. This prevents a cheater from attempting to retrieve the token at a later time.
  • Players that trigger a server event without a valid security token are kicked from the game.

Usage

The security token is stored in a variable named securityToken on the client side in each resource. In order to retreive the security token for a given resource, you must include the init.lua script in your resource’s __resource.lua file. The init.lua script must be included as both a server and client script:

server_script '@salty_tokenizer/init.lua'
client_script '@salty_tokenizer/init.lua'

Note: If you implemented salty_tokenizer prior to the init.lua script being released, it will continue to function normally and no changed need to be made.

Client
Once the token is received, it can be passed along with a server event to be validated on the server-side.

TriggerServerEvent('anticheat-testing:testEvent', securityToken)

Server
In order to protect a server event, a simple if statement must be added.

RegisterNetEvent('anticheat-testing:testEvent')
AddEventHandler('anticheat-testing:testEvent', function(token)
	local _source = source
	if not exports['salty_tokenizer']:secureServerEvent(GetCurrentResourceName(), _source, token) then
		return false
	end
	print("Authenticated")
end)

This is expanded on in this post: [Release][DEV] Server Event Security Tokens - Anticheat

Deprecated Usage 9/5/2018

Usage
In order to use this resource, both the client and server scripts need adjusted.

Client
Tokens can be received by triggering an export once the tokenizer is ready.

local securityToken = nil
AddEventHandler('salty_tokenizer:clientReady', function()
	securityToken = exports['salty_tokenizer']:setupClientResource(GetCurrentResourceName())
end)

Once the token is received, it can be passed along with a server event to be validated on the server-side.

TriggerServerEvent('anticheat-testing:testEvent', securityToken)

Server
To prepare a resource server side, an export must be called when the script is ready. Once this export is called, server events can be easily protected against abused server events.

AddEventHandler('salty_tokenizer:serverReady', function()
	exports['salty_tokenizer']:setupServerResource(GetCurrentResourceName())
end)

In order to protect a server event, a simple if statement must be added.

RegisterNetEvent('anticheat-testing:testEvent')
AddEventHandler('anticheat-testing:testEvent', function(token)
	local _source = source
	if not exports['salty_tokenizer']:secureServerEvent(GetCurrentResourceName(), _source, token) then
		return false
	end
	print("Authenticated")
end)
Deprecated Usage 8/1/2018

Usage
In order to use this resource, both the client and server scripts need adjusted.

Client
Tokens are received through the salty_tokenizer:receiveTokenClient event on the client. A listener must be setup to store this security token.

local securityToken = nil

RegisterNetEvent('salty_tokenizer:receiveTokenClient')
AddEventHandler('salty_tokenizer:receiveTokenClient', function(receivedToken)
	securityToken = receivedToken
end)

Events triggered with TriggerServerEvent must now be modified to pass the security token along.

TriggerServerEvent('resource:securedEvent', securityToken)

Server
Tokens are received through the salty_tokenizer:receiveTokenServer event on the server. A listener must be setup to store this security token.

local securityToken = nil

RegisterNetEvent('salty_tokenizer:receiveTokenServer')
AddEventHandler('salty_tokenizer:receiveTokenServer', function(receivedToken)
	securityToken = receivedToken
end)

Events triggered from the client must now be modified to confirm the security token. If there is a security token mismatch, you may trigger the salty_tokenizer:invalidToken event in order to kick the offending player: TriggerEvent('salty_tokenizer:invalidToken', _source)

RegisterNetEvent('resource:securedEvent')
AddEventHandler('resource:securedEvent', function(token)
	local _source = source
	if token ~= securityToken then
		TriggerEvent('salty_tokenizer:invalidToken', _source)
		return false
	end

	print("Authenticated")
end)

Download
Available on GitHub - https://github.com/SaltyGrandpa/salty_tokenizer

34 Likes

Cant they just add an event handler for when you send the token back and just intercept it?

To my knowledge, they typically use an injector that allows for triggering of events, etc. but not a listener. In theory, if they are listening when you receive the token I suppose you could reuse it, yes. I would suggest renaming the events in order to prevent against listening (unless they use a decryptor and find this). This should discourage most script kiddies that just trigger known server events.

You should always use best practice and make any sensitive events server side only, but if there is a necessity to make it accessible client side, this is an extra layer to protect against that. Security should be treated like an onion. Add several layers to make it as difficult as possible to exploit.

2 Likes

The injector can literally do anything a client side script can

That’s fine. I don’t expect this to be a foolproof solution. Again, this is an additional layer to attempt to mitigate script kiddies. If someone finds it helpful, great; if not, at least it wasn’t another vehicle conversion release. :wink:

5 Likes

Cant complain with that, fair enuff.

2 Likes

Current implementations run in a ‘random’ script context. This usually isn’t the script context that uses this system.

So technically they can just do “print(securityToken)”?

No, because that’s received and stored in a script. They could technically have a listener that listens for when it receives a key to be able to know what the token is and use it when they trigger a server event.

I’ll probably add an optional salt in that you can add to your scripts to mitigate this further. They could still in theory decrypt that and figure out the salt and token. Nothing is foolproof, but again, this will quickly weed out the script kiddies and need to be an actual targetted cheater.

2 Likes

Sounds good :smiley:

Just assume you can never trust the client and consider that when you make resources. This is for worst case scenarios when you absolutely need feedback from the client.

5 Likes

The best would be that each resource is are own tockent to slow down even more kids

Correct. That’s the approach I personally take, using similar tokens to what’s created in this resource. Each resource has their own, uniquely named listener with their own unique tokens. Someone would have to decrypt each resource to find the name of the listener, listen for it (which can only happen once), and then repeat it. It would be very tedious and definitely eliminates the vast majority.

2 Likes

@SaltyGrandpa laallalallalallalalloooooove it ;))))

I have mitigated the possibility of a cheater listening for the event by sending it to a uniquely obfuscated client event. They would have to first listen for the event to be created to know what event to listen for, and by that point it would be too late as they cannot trigger it again.

I will work on committing the changes and documenting the changes after testing thoroughly to ensure no issues.

1 Like

It would be great if there was a tutorial on this would be great for people who are not 100% sure on how to secure their servers.

2 Likes

Agreed. I want to make it as simple as possible while still remaining secure. This latest version I’m working on is a substantial improvement in security, but makes it more difficult to work with. I’m working on making it much simpler to integrate with existing scripts (exports are awesome).

Update 8/1/2018

  • Mitigated the ability to intercept security tokens like mentioned by @sadboilogan . Keys are now passed through an obfuscated client event that is created every time a player joins.
  • Added support for using exports to make implementation much easier.
  • Each resource gets their own security key now to make it even harder to trigger server events.

All documentation has been updated to reflect the changes. The files have been committed to GitHub.

Note that while I do test this on my server, I cannot guarantee you won’t have issues when implementing. I would suggest implementing this one resource at a time and confirming functionality after each update.

3 Likes

after reading it… i understood…

A few questions… hope you can answer them :slight_smile:
if a server script triggers another server event… must the token be passed there via server?

and What if a Client Event is triggered?

in an environment like ESX if i trigger a server event that is in another script… what should happen?

how do i do for those script with server only lua files?

1 Like