1

Let's say I want a Lua table that will be provided from a third party, not totally reliable, from a file or other IO source.

I get the table as a string, like "{['valid'] = 10}" and I can load it as

externalTable = loadstring("return " .. txtTable)()

But this opens a breach to code injection, ie.: txtTable = os.execute('rm -rf /')

So I did this sanitizing function:

function safeLoadTable(txtTable)
    txtTable = tostring(txtTable)
    if (string.find(txtTable, "(", 1, true)) 
        then return nil end
    local _start = string.find(txtTable, "{", 1, true)
    local _end = string.find(string.reverse(txtTable), "}", 1, true)
    if (_start == nil or _end == nil)
        then return nil end
    txtTable = string.sub(txtTable, _start,  #txtTable - _end + 1)
    print("cropped to ", txtTable)
    local pFunc = loadstring("return " .. txtTable)
    if (pFunc) then
        local _, aTable = pcall(pFunc)
        return aTable
    end
end

In the worst case it should return nil. Can this be considered safe against a "regular bad-intentioned person" :)

hjpotter92
  • 78,589
  • 36
  • 144
  • 183
gabriel_agm
  • 513
  • 1
  • 4
  • 14

4 Answers4

1

I don't think it is safe. Try this:

print(safeLoadTable [[{ foo = (function() print"yahoo" end)() } ]])

EDIT

or this, for more fun:

print(safeLoadTable [[{ foo = (function() print(os.getenv "PATH") end)() } ]])

I won't suggest the alternative of replacing that os.getenv with os.execute, though. :-)

The problem is not easy to solve. Code injection avoidance is not at all simple in this case because you are executing a piece of Lua code when doing that loadstring. No simple string matching technique is really safe. The only secure way would be to implement a parser for a subset of the Lua table syntax and use that parser on the string.

BTW, even Lua team stripped off the bytecode verifier from Lua 5.2 since they discovered that it was amenable to attacks, and bytecode is a far simpler language than Lua source code.

1

You could run the unsafe code in a sandbox.

Here is how a simple sandbox could look in Lua 5.1 (error handling omitted for brevity):

local script = [[os.execute("rm -rf /")]]
local env = { print=print, table=table, string=string }
local f, err = loadstring(script)
if err then
   -- handle syntax error
end
setfenv(f, env)
local status, err = pcall(f)
if not status then
   -- handle runtime error
end

In Lua 5.2 you can load the script into it's own environment using the load function.

The result would be a runtime error returned from pcall:

attempt to index global 'os' (a nil value)

EDIT

As Lorenzo Donati pointed out in the comments this is not a complete solution to stop rogue scripts. It essentially allows you to white-list functions and tables that are approved for user scripts.

For more info about handling rogue scripts I would suggest this SO question: Embedded Lua - timing out rogue scripts (e.g. infinite loop) - an example anyone?

Community
  • 1
  • 1
Adam
  • 3,053
  • 2
  • 26
  • 29
1

I created sandbox.lua for exactly this purpose. It'll handle both insecure stuff as well as DOS-type attacks, assuming that your environment has access to the debug facility.

https://github.com/kikito/sandbox.lua

Note that for now it is Lua 5.1-compatible only.

kikito
  • 51,734
  • 32
  • 149
  • 189
0

Running in sandbox isn't safe, inspecting source code is not very simple. An idea: inspect bytecode!

Emmm, actually that's not very simple either, but here is a lazy implementation: http://codepad.org/mGqQ0Y8q

mpeterv
  • 463
  • 4
  • 11