EZ-Inventory clientside lua backdoor

EZ-Inventory has a backdoor in it that allows people to basically bypass the script enforcer and to execute any clientside lua files.
It can be found in the file EZ_Notification.lua:

EZI.Notif = {}
local notfile = "ezi/ez_notifications.txt"

function EZI.Notif.LoadTable()
	if file.Exists( notfile, "DATA" ) then
		local content = file.Read(notfile, "DATA")
		EZI.Notifications = Deserialize(content)
function EZI.Notif.WriteTable()
	local notiftbl = Serialize(EZI.Notifications)
	if !file.Exists( notfile, "DATA" ) then
		file.CreateDir( "ezi" )
	file.Write( notfile, notiftbl )
function Deserialize(sIn)
	SRL = nil


	return SRL

This is really easy to exploit, you just open the ez_notifications.txt and basically replace the content with any lua scripts and
it will be executed. Example:

local PANEL = {}

function PANEL:Init()
	self:SetSize(600, 800);
	self:SetTitle("Lua Interface")
	self.TextEntry = vgui.Create("DTextEntry", self)
	self.TextEntry:SetPos(20, 60)
	self.TextEntry:SetSize(self:GetWide()-42, 700)
	self.SendButton = vgui.Create("DButton", self)
	self.SendButton:SetSize(120, 25)
	self.SendButton:SetPos(self:GetWide()/2-self.SendButton:GetWide()/2, self:GetTall() - self.SendButton:GetTall() - 7)
	self.SendButton.DoClick = function()

vgui.Register("LuaInterface", PANEL,  "DFrame")

concommand.Add("ShowLuaInterface", function(ply)

This allows you to execute any other lua code while you are on the server.

I have tried to contact the author of the script, but unfortunately he hasn’t accepted me within 1 month so that is
why I posted it here. I also want to point out that this probably isn’t a deliberately placed backdoor, even though
it is really weird and questionable to save information like that and using runstring to read it again.


Who cares. Anyone can dl the 785 bypasses. This is pretty petty

That’s the stupidest file reading system ever. How did it even get on CH with that?

With a Deserialize function like that, I worry about how bad the Serialize function is and if we can execute Lua on other clients (admins).

I wonder who made the script.

Serializing to Lua and loading it by run string isn’t a particulary bad practice, other addons like PAC do this as well. If the client really wants to run Lua they can use any of the bypassers as zero mentioned, this can hardly be seen as an exploit. Any proper Lua anticheat will stop hacks loaded like this.

There are some security concerns of course, a thing i like to do is use setfenv and CompileFile to “sandbox” the deserialization

Anticheats aren’t needed when you have solid code. They also can’t prevent most of those things unless they disallow runstring completely and trust me, they are not as good as you think.

Btw. I use this backdoor on a regularly basis on servers and it just shouldn’t be there. There are really easy ways to store information. So why does the author use lua code for that?

Most lua anticheats can very easily detect most known cheats. People who know what they are doing don’t need an “exploit” like this to run cl Lua code. I’m well aware of how they work.

Using serialized lua/luadata has the advantage of being extremely portable, you don’t need any complex way of deserializing, it is very easy to read and edit manually and extremely quick to deserialize. Furthermore data serialized this way doesn’t depend on specific versions on a serializing/deserializing script as it’s just direct code. Of course this needs to be patched (for example through setfenv as suggested) but it really isn’t a “clientside lua backdoor”.

Who are you OP? You’re like the backdoor-hunter.

How is using a new fenv gonna sandbox it or more like what’s the point of sandboxing it?
I mean I can always do getfenv to get your fenv, but I don’t really mind running my lua hax in your fenv since that’s gonna change nothing but hide me a bit.

What exactly is ‘fenv’?

Function environment
This sums it up pretty nicely

Basically the coolest use of it is to be able to create global variables that just exist in your context.

Using bypasser = actually chance of being banned, even if remote at the moment.

Having one perfectly safe to run = issue.

The remote possibility of being banned stops a lot of minges from wanting to do it, I know remote and cheap alts etc. But it’s still a factor.

if someone is smart enough to learn how to load lua via text file. I really dont think he’s stupid enough to use a bypasser which wont get him banned

yeah good luck with that, you won’t have access to any of the _G functions, you’re run in an empty table


Hey I was messaged this to me by adam (owner of CoderHire).

This is obviously really embarrassing and this is definitely not something I intended.
The serialize function that I used was originally only for the server in loading the inventory tables in which this code will suffice. However I then added the notifications afterwards in later versions and I didn’t see a problem with using the same functions, obviously I was wrong in choosing that. I didn’t really think it through as I was trying to make it all work nicely. I’ve tried to keep my stuff as exploit free as possible and as far as I know it is other than this.

I am working to fix this issue and I will be releasing an update and will try and contact as many people to let them know about this. As far as not getting back to OP, I have over 70 Steam Invites and I don’t have time to go through and accept them all. I mainly talk to people about problems on CoderHire via pm and then add if needed. If I had been PM’d about this I would have gotten back to you.

And as far as I know the serialize function is fine and doesn’t use runstring so I’m going to modify the deserialize function and fix the exploit.

EDIT: As of now I have released the new version which patches this issue and this backdoor now no longer works.
Thank you slyl0r for reporting this!

Huh? If you pass an empty fenv then nothing in it is gonna work, but if you wanted to “sandbox” something like this you would oubviously pass _G to it.

[lua]local newFenv = {} --this will be our new fenv
setmetatable(newFenv, {__index = _G}) --copying _G into our new fenv
setfenv(1, newFenv) --making our new fenv active[/lua]

And then you can access all global functions with newFenv?
example being newFenv.print()? Yeah?

No, basically the table you pass as the second arg will become the new _G.
However if you just do
[lua]setfenv(1, _G)[/lua]
Then you’ll always have to do stuff like
[lua]_G.print(“Hello world!”)[/lua]
By passing a table that has _G as __index we can avoid that and just code like we are used to.