0

Currently, I am writing a sandbox in Lua. It's working so far, but I could use getfenv for that function to get the scope outside the sandbox. For my sandbox, I filled an empty table with trusted functions and libraries. However, with functions such as print, you could use getfenv to get the global variable(s) in that scope. For example:

asd = "asd"
assert(pcall(assert(load([[
print(getfenv(print).asd) -- "asd"
]], nil, "t", {print = print, getfenv = getfenv}))))

This can obviously allow the "adversary" to bypass the sandbox.

Egor Skriptunoff
  • 23,359
  • 2
  • 34
  • 64
seniwo
  • 21
  • 5

1 Answers1

1

You need to write a wrapper for getfenv that prevents leaking the unsandboxed environment, like MediaWiki's Scribunto extension does:

    local function my_getfenv( func )
        local env
        if type( func ) == 'number' then
            if func <= 0 then
                error( "'getfenv' cannot get the global environment" )
            end
            env = old_getfenv( func + 1 )
        elseif type( func ) == 'function' then
            env = old_getfenv( func )
        else
            error( "'getfenv' cannot get the global environment" )
        end

        if protectedEnvironments[env] then
            return nil
        else
            return env
        end
    end

The gist of it is that you check the returned environment, and if it's a protected environment (e.g., _G), then refuse to return it. The only trick is handling the argument, since it means different things depending on its type, and it's sensitive to the extra function in the call stack.