Як я можу створити безпечну пісочницю Lua?


74

Отже, Lua здається ідеальним для впровадження безпечних «скриптів користувача» всередині моєї програми.

Однак більшість прикладів вбудовування lua, здається, включають завантаження всіх стандартних бібліотек, включаючи "io" та "package".

Тож я можу виключити ці бібліотеки з мого інтерпретатора, але навіть базова бібліотека включає функції "dofile" і "loadfile", які отримують доступ до файлової системи.

Як я можу видалити / заблокувати будь-які небезпечні функції, подібні до цих, не отримуючи просто інтерпретатор, який навіть не має базових матеріалів, таких як функція "ipairs"?

Відповіді:


53

Ви можете встановити середовище функції, в якому запускається ненадійний код, за допомогою setfenv (). Ось схема:

local env = {ipairs}
setfenv(user_script, env)
pcall(user_script)

user_scriptФункція може отримати доступ тільки те , що в його оточенні. Тоді ви можете явно додати до функцій, до яких ви хочете мати доступ до ненадійного коду (білий список). В цьому випадку сценарій користувач має доступ тільки до , ipairsале нічого ( dofile, loadfileі т.д.).

Див. Lua Sandbox для прикладу та додаткової інформації про пісочницю lua.


18
Зауважте, я вважаю, що це повинно бути local env = {ipairs=ipairs}. І якщо ви запускаєте це на інтерактивній lua cli, оберніть все це в do ... endпетлю, щоб не втратити місцеві варіації.
BMitch

9
Слід зазначити, що це спосіб дії Lua 5.1.
John K

У Lua 5.2 і вище ви будете використовувати load()з envаргументом замість, який є набагато більш безпечною альтернативою в setfenv()будь-якому випадку , тому що ви не можете забути його як легко.
DarkWiiPlayer

34

Ось рішення для Lua 5.2 (включаючи зразок середовища, яке також працює в 5.1):

-- save a pointer to globals that would be unreachable in sandbox
local e=_ENV

-- sample sandbox environment
sandbox_env = {
  ipairs = ipairs,
  next = next,
  pairs = pairs,
  pcall = pcall,
  tonumber = tonumber,
  tostring = tostring,
  type = type,
  unpack = unpack,
  coroutine = { create = coroutine.create, resume = coroutine.resume, 
      running = coroutine.running, status = coroutine.status, 
      wrap = coroutine.wrap },
  string = { byte = string.byte, char = string.char, find = string.find, 
      format = string.format, gmatch = string.gmatch, gsub = string.gsub, 
      len = string.len, lower = string.lower, match = string.match, 
      rep = string.rep, reverse = string.reverse, sub = string.sub, 
      upper = string.upper },
  table = { insert = table.insert, maxn = table.maxn, remove = table.remove, 
      sort = table.sort },
  math = { abs = math.abs, acos = math.acos, asin = math.asin, 
      atan = math.atan, atan2 = math.atan2, ceil = math.ceil, cos = math.cos, 
      cosh = math.cosh, deg = math.deg, exp = math.exp, floor = math.floor, 
      fmod = math.fmod, frexp = math.frexp, huge = math.huge, 
      ldexp = math.ldexp, log = math.log, log10 = math.log10, max = math.max, 
      min = math.min, modf = math.modf, pi = math.pi, pow = math.pow, 
      rad = math.rad, random = math.random, sin = math.sin, sinh = math.sinh, 
      sqrt = math.sqrt, tan = math.tan, tanh = math.tanh },
  os = { clock = os.clock, difftime = os.difftime, time = os.time },
}

function run_sandbox(sb_env, sb_func, ...)
  local sb_orig_env=_ENV
  if (not sb_func) then return nil end
  _ENV=sb_env
  local sb_ret={e.pcall(sb_func, ...)}
  _ENV=sb_orig_env
  return e.table.unpack(sb_ret)
end

Потім, щоб скористатися нею, ви зателефонуєте своїй функції ( my_func) таким чином:

pcall_rc, result_or_err_msg = run_sandbox(sandbox_env, my_func, arg1, arg2)

1
Чому б не використовувати setfenv? Я новачок у Lua, тому мені цікаво, в чому різниця.
Річка Ліліт

9
@Computer Linguist: setfenvвидалено з 5.2: lua.org/work/doc/manual.html#8.2
BMitch

1
Я намагаюся запустити ваш код, але я не розумію, де б ви оголосили my_func і як для того, щоб зробити цю роботу. Це гавкає, що "42: спроба індексувати значення" e "(
нульове

1
Це не працює. Функції використовують _ENV, з яким вони були скомпільовані, а не _ENV, з якого вони були викликані. Вам потрібно викликати debug.setupvalue (sb_func, 1, sb_env), щоб замінити його _ENV, перш ніж викликати його.
Джон К

1
Заміна _ENV після завантаження функції - це спосіб 5.1. У 5.2 ви просто передаєте sb_env як параметр ENV для "завантаження", тобто. load ("function sb_func () return nil end", "", "t", sb_env), тоді ви можете просто кожного разу викликати sb_func як звичайну функцію.
Джон К


5

Один з найпростіших способів усунути небажане - це спочатку завантажити сценарій Lua, який ви розробляєте самостійно, який робить такі речі:

load = nil
loadfile = nil
dofile = nil

Крім того, ви можете використовувати setfenvдля створення обмеженого середовища, в яке можна вставити певні безпечні функції.

Повністю безпечний пісочниця трохи складніше. Якщо ви завантажуєте код звідки завгодно, майте на увазі, що попередньо скомпільований код може розбити Lua. Навіть повністю обмежений код може потрапити в нескінченний цикл і блокувати на невизначений час, якщо у вас немає системи його вимкнення.


Насправді вам не потрібно завантажувати скрипт Lua, щоб збити щось нанівець - ви можете використовувати функції Lua API, про які я згадав у своїй відповіді, щоб занулити глобальні дані поза межами Lua.
Бурштин

Дійсно, але це в чомусь легше, звідси і "найпростіша" кваліфікація.
Джон Калсбек,

3

Ви можете використовувати lua_setglobalфункцію, надану API Lua, для встановлення тих значень у глобальному просторі імен, nilякі ефективно запобігатимуть доступу будь-яких сценаріїв користувачів до них.

lua_pushnil(state_pointer);
lua_setglobal(state_pointer, "io");

lua_pushnil(state_pointer);
lua_setglobal(state_pointer, "loadfile");

...etc...

7
з точки зору безпеки, я ніколи не довіряв би рішенням, внесеним до чорного списку (я можу просто забути якусь функцію, яка є неприйнятною), принаймні, коли у мене є доступні рішення з білого списку (див. деякі відповіді вище).
Девід

1

Якщо ви використовуєте Lua 5.1, спробуйте:

blockedThings = {'os', 'debug', 'loadstring', 'loadfile', 'setfenv', 'getfenv'}
scriptName = "user_script.lua"

function InList(list, val) 
    for i=1, #list do if list[i] == val then 
        return true 
    end 
end

local f, msg = loadfile(scriptName)

local env = {}
local envMT = {}
local blockedStorageOverride = {}
envMT.__index = function(tab, key)
    if InList(blockedThings, key) then return blockedStorageOverride[key] end
    return rawget(tab, key) or getfenv(0)[key]
end
envMT.__newindex = function(tab, key, val)
    if InList(blockedThings, key) then
        blockedStorageOverride[key] = val
    else
        rawset(tab, key, val)
    end
end

if not f then
    print("ERROR: " .. msg)
else
    setfenv(f, env)
    local a, b = pcall(f)
    if not a then print("ERROR: " .. b) end
end

4
Я не можу коментувати техніку пісочниці, але я б запропонував зробити заблоковані речі більш схожими на {os = true, debug = true}, тож це набір, тоді перевірка просто якщо заблоковані речі [ключ], а ви ні потрібна функція InList.
mlepage

-2

Ви можете замінити (вимкнути) будь-яку функцію Lua, яку хочете, а також використовувати метатаблиці для більшого контролю .


14
Метатаблиці можна обійти через rawget () і не слід використовувати для захисту, лише для зручності.
Бурштин

Ви не можете замінити rawget, якщо хочете?
RCIX,

RCIX, ти можеш це зробити. У вас є свобода робити майже все, що завгодно в Lua :-)
Нік Дандулакіс

1
Ви можете перевизначити rawget, але це також порушить не зловмисні метадані функціональні можливості, і це не ідеальне рішення.
Amber
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.