Loadstring() for one specific restricted use - any security risks?

In a game I am making I have a Script in the ServerScriptService to construct a string representing a mathematical formula and calculate it. In order to calculate the constructed formula, I have two options:

  1. Program a function that takes as a parameter one string with the format of a mathematical formula and evaluates each part of it recursively until it has all been calculated and return the final result number.
  2. Load the constructed string with the format of a mathematical formula using the Loadstring() function.

Ignoring the security risks for a moment, when comparing the two options the Loadstring() is exceedingly better in every way - I can use the math library, I don’t need to spend hours upon hours to implement a string calculator, and faster calculation times.

But when making a game I cannot ignore the security risks Loadstring() is so known for, which leads me to my question:
If the player has no way to directly change the string that will be loaded into the Loadstring(), and even when changing the string indirectly via progressing in the game (and making the game construct a different string to load) it is still always strings that are already set in the code as variables and cannot be edited in any way other than by me from Roblox Studio. The Loadstring() function will be used in my game only in that one specific place, in only one line, in a Script running on the server and parented to ServerScriptService. This script also has no interactions at all with other scripts and only outputs its results by setting NumberValues in a folder inside the ServerStorage.
Will it pose any security risks to enable Loadstring() solely for this purpose?

It depends. If the various strings can still somehow be concatenated into words, then it may be possible to execute lambda functions:

loadstring [[
    (function()
        print('insert malicious code here')
    end)()
]]()

So you should definitely still use some sort of blacklist to filter out keywords to prevent that from happening; especially for, while, and repeat since they can infinitely loop to freeze the server.

Also, always override the loadstring environment.

local f = loadstring[[return game]]
setfenv(f, {})
print(f()) --nil

If the player has no way to directly change the string that will be loaded into the Loadstring()

You should be fine. Do not put any text the player controls into loadstring, anything from a remote event should never be used, hackers can send any text they want through remote functions and events. Even their username could be a exploitive payload.

1 Like

If it is concatenated by my code running on a server side script, is it even possible for client side exploits to manipulate it?

Practically not, but it is not impossible;

You could just get unlucky where it accidentally creates an infinite loop. As unlikely as that may sound, you can never be too safe and should still use a blacklist filter to block certain keywords.

I am always very selective and cautious with what I send over remote events/functions, but I guess scripts with loadstring require a totally different league of cautiousness.

Any idea for keywords I should make a blacklist for? Of course any word that has something to do with the DatastoreService and MemoryStoreService will go in it, but is there anything else I should add?

I already gave you a few in my previous post. You can just scan through the Lua manual and add anything that sounds dangerous:
image

This will not be necessary if you override/delete the loadstring environment (also mentioned in my previous post) so that the function is incapable of accessing any Roblox-related stuff, even builtin functions like print.

1 Like

So only Lua words, nothing related to Roblox words as long as I set the proper loadstring environment.

basically yeah but there’s nothing stopping you from adding Roblox words to the filter too

You can also use the setfenv function to limit what the function that was returned has access to. I believe that this is what the game lua learning did also. I could be wrong though.

As for the setfenv(), I’ve never used it before. In the case formulaStr is the calculation string and env is the environment level, what should I set env as for the code below?

setfenv(assert(loadstring(formulaStr)),env)

env should be a dictionary where the variable name is the key, and the variable value is the value. For example, here’s how you “spy” on the print function:

local env = {
	print = function(...)
		warn(`there are {select('#', ...)} parameters passed to print`)
		print(...)
	end,
}

local code = [[
	print('hello', 'world')
]]

local f = loadstring(code)
setfenv(f, env)
print(f())

image

You would put a table with a list of all of the math functions or whatever other functions you want it to access. Something like this:

local allowed = {
    math.sin,
    math.cos,
   --...
}

you can see what it does if you try this:

local f = function()
	print("Hello World!")
end

setfenv(f, {})
f()

The setfenv makes it so that the function cannot access any global functions

Enabling loadstring does not pose any security risks unless you have a RemoteEvent that directly lets someone access the loadstring function on the server from the client. A way you can make sure that a string passed to loadstring doesn’t contain anything that could damage the server is by checking if the string contains any alphabetical characters.

local function IsSafe(String)
    String = string.gsub(String, "x", "*") -- Replace x with * since people tend to use it for multiplication

    return string.find(String, "%a") == nil   -- checks if the string has any letters, if it doesn't the function returns true and the string should be safe to use in loadstring()
end

print(IsSafe("(1 + 2) x 5")) -- true since it doesn't contain any letters besides x, which is whitelisted for multiplication.
print(IsSafe("workspace:ClearAllChildren()")) -- false since it contains letters

So for running the loadstring in a safe environment, I just need to call setfenv with the loadstring and an empty dictionary?

setfenv(f,{})
print(f())

That will only slow down your script, you should just check if the script contains any letters instead like I explained in my reply above

In the code I will load with loadstring() there are going to be functions from the math library, so instead I will make a copy of the string with all the math function words (I will make a list of allowed words) removed and then check for any remaining letters.
Shouldn’t be a problem if I do it like that to whitelist a few words, right?

Just sanitize the string and you’ll be just fine.

Thanks to all of you, I got my solution now. Thanks a lot.