Securing addons in gLUA

Introduction

Never trust the client!

That's basically the only rule that you have to know. Never trust the client, always verify what the client sends to the server. A client can always send anything they want, whenever they want and to any net channel they want. Checking for admin clientside won't help, because players can simply run the net.Send function anyways if they have the proper tools.

There are 2 main ways to exploit servers nowadays: Either with net message shenanigans or via SQL injections (which are sometimes done via net messages)

net messages

Net message exploits are one of the most common exploits in gLUA. This is because the programers have to secure net.Receive functions themselves.

The following code is a good example of how to do it right:

local validWeapons = {
  ["weapon_m42"] = true,
}
net.Receive("police_armory_retrieve", function(len, ply)
  local ent = net.ReadEntity()
  local weaponClass = net.ReadString()
  
  --[1] spam delay
  if not ply.armoryTimeout then ply.armoryTimeout = 0 end
  if ply.armoryTimeout > CurTime() then return end
  ply.armoryTimeout = CurTime() + 10
  
  --[2] check team
  if ply:Team() ~= TEAM_POLICE then return end
  
  --[3] check entity and distance
  if not IsValid(ent) then return end
  if ent:GetClass() ~= "sent_police_armory" then return end
  if ply:GetPos():Distance(ent:GetPos())) > 512 then return end
  
  --[4] check requested weapon
  if not validWeapons[weaponClass] then return end
  
  --do stuff
  ply:Give(weaponClass)
end)

This code checks everything that needs to be checked before giving the sending player his weapon.
The first check (code block) is against net spam. You can only retrieve a weapon from the police armory every 10 seconds.
The second code block checks if the player is even in the right team to retrieve things from the police armory.
The third block checks the entity. Did the player send a valid entity? Is it a police armory? Is he close enough to interact with it? (we need a valid entity to calculate the distance between it and the player)
The last check is about the weapon the player wants to retrieve. We shouldn't let the player take out any weapon they want, this could lead to exploits too.


Now that we know how a good net.Receive function looks like: Let's go over a bad one.
Warning: The following code example is bad and should NOT be used!

net.Receive("police_armory_retrieve", function(len, ply)
  local officer = net.ReadEntity()
  local weaponClass = net.ReadString()
  officer:Give(weaponClass)
end)

The code example above simply gives any player that sends the net message any weapon they requested.

Let's summarize the problems of the code example above.

The net.Receive function from above doesn't check the job of the person that tried to retrieve the weapon from the armory. This means that any player, even as a Civilian job, can send the following code and get any weapon they want, in this case an AK47:

net.Start("police_armory_retrieve")
  net.WriteEntity(LocalPlayer())
  net.WriteString("weapon_ak472")
net.SendToServer()

The best part about this: The player who receives the weapon is being sent via net.WriteEntity(). This means you can send any player you want and he will receive the weapon you requested.

All of this can also be done while standing at spawn AFK as a Civilian job. The last thing: The code has no cooldown for retrieving weapons. This means you can spam the server with weapon retrievals and get all the weapons you want.

A summary of the bad code example above: You can give every player on the Server any weapon you want for free as a Civilian at spawn with no delay inbetween.

An example exploit for the bad net.Receive function above, which gives every player on the server the "davy croket" nuke weapon:

for k,v in pairs(player.GetAll()) do
  net.Start("police_armory_retrieve")
    net.WriteEntity(v)
    net.WriteString("weapon_davy_croket")
  net.SendToServer()
end

SQL Injections

SQL Injections are not common anymore as they are the easiest to detect and fix.
Let's create an example to show how easy it is to fix common SQL Injections. The following is a function that updates the players character name in the database: (this is the BAD example!)

[...]
sql.Query("UPDATE darkrp_player SET rpname = '"..name.."' WHERE uid = '"..ply:SteamID64().."'")
[...]

This function runs an SQL Query that updates the database with the new name of the player. The uid for the player is his SteamID 64, which only consists of numbers.

Now let's go over the bad things about this SQL code.
First things first: Never save player input directly into your database!
You should ALWAYS escape player input. Escaping means to "remove dangerous text and symbols so that the text is save to be inserted into a database".
In GMod for SQLite you have the sql.SQLStr function for this. This function makes any text save to insert.
Example: sql.Query("UPDATE tableX SET name = '"..name.."' WHERE uid = 3) will be made to this: sql.Query("UPDATE tableX SET name = "..sql.SQLStr(name).." WHERE uid = 3").

So for a shorter example: This '"..name.."' will be made to this ..sql.SQLStr(name)... (Watch the single commas!)
The function will add the single high commas for you, so do NOT keep them!

This would make our bad function from above to this:

[...]
sql.Query("UPDATE darkrp_player SET rpname = "..sql.SQLStr(name).." WHERE uid = "..sql.SQLStr(ply:SteamID64()))
[...]