Python: защита ненадежных скриптов/подпроцессов с помощью chroot и chjail?

Я пишу веб-сервер на основе Python, который должен иметь возможность запускать «плагины», чтобы функциональность можно было легко расширить.

Для этого я рассмотрел подход, заключающийся в том, чтобы иметь несколько папок (по одной для каждого плагина) и несколько сценариев оболочки/питона, названных в соответствии с предопределенными именами для различных событий, которые могут произойти.

Одним из примеров является наличие файла on_pdf_uploaded.py, который выполняется при загрузке PDF-файла на сервер. Для этого я бы использовал инструменты Python subprocess.

Для удобства и безопасности это позволило бы мне использовать переменные среды Unix для предоставления дополнительной информации и установить рабочий каталог (cwd) процесса, чтобы он мог получить доступ к нужным файлам без необходимости находить их местоположение.

Поскольку код плагина исходит из ненадежного источника, я хочу сделать его максимально безопасным. Моя идея состояла в том, чтобы выполнить код в подпроцессе, но поместить его в тюрьму chroot с другим пользователем, чтобы он не мог получить доступ к каким-либо другим ресурсам на сервере.

К сожалению, я ничего не смог найти об этом, и я не хотел бы полагаться на ненадежный скрипт, чтобы посадить себя в тюрьму.

Кроме того, я также не могу поместить основной/вызывающий процесс в chroot-тюрьму, поскольку код плагина может выполняться в нескольких процессах одновременно, пока сервер отвечает на другие запросы.

Итак, вот вопрос: как я могу выполнять подпроцессы/скрипты в chroot-тюрьме с минимальными привилегиями, чтобы защитить остальную часть сервера от повреждения ошибочным, ненадежным кодом?

Спасибо!


person BastiBen    schedule 30.07.2012    source источник
comment
Это действительно твоя работа? Разве они не должны знать, какой код они используют? Как бы то ни было... Это помогает? os.chroot(). Кроме того, у os есть плюшки, которые можно использовать с uid и т. д. Итак, создайте новый процесс (os.fork()?), затем os.setuid, затем os.execle().   -  person Logan    schedule 30.07.2012


Ответы (2)


После создания вашей тюрьмы вы должны вызвать os.chroot из исходного кода Python, чтобы войти в нее. Но даже в этом случае любые разделяемые библиотеки или файлы модулей, уже открытые интерпретатором, все равно будут открыты, и я понятия не имею, к каким последствиям приведет закрытие этих файлов через os.close; Я никогда не пробовал.

Даже если это сработает, настройка chroot — это большое дело, поэтому убедитесь, что выгода того стоит. В худшем случае вам придется убедиться, что вся среда выполнения Python со всеми модулями, которые вы собираетесь использовать, а также все зависимые программы и общие библиотеки и другие файлы из /bin, /lib и т. д. доступны в каждой файловой системе, находящейся в тюрьме. И, конечно же, это не защитит другие типы ресурсов, то есть сетевые адресаты, базу данных.

Альтернативой может быть чтение ненадежного кода в виде строки, а затем exec code in mynamespace, где mynamespace — это словарь, определяющий только те символы, которые вы хотите предоставить ненадежному коду. Это было бы своего рода «тюрьмой» внутри виртуальной машины Python. Возможно, вам придется сначала проанализировать исходный код в поисках таких вещей, как операторы import, если только замена встроенной функции __import__ не перехватит это (я не уверен).

person wberry    schedule 30.07.2012
comment
Я выбрал подход с динамической загрузкой кода, потому что он обеспечивает наилучшую гибкость обмена данными и обработки исключений. - person BastiBen; 22.09.2012

Возможно, что-то вроде этого?

# main.py
subprocess.call(["python", "pluginhandler.py", "plugin", env])

Потом,

# pluginhandler.py
os.chroot(chrootpath)
os.setgid(gid) # Important! Set GID first! See comments for details.
os.setuid(uid)
os.execle(programpath, arg1, arg2, ..., env)
# or another subprocess call 
subprocess.call["python", "plugin", env])

РЕДАКТИРОВАТЬ: Хотел использовать fork(), но я не совсем понял, что он делает. Посмотрел. Новый код!

# main.py
import os,sys
somevar = someimportantdata
pid = os.fork()
if pid:
    # this is the parent process... do whatever needs to be done as the parent
else:
    # we are the child process... lets do that plugin thing!
    os.setgid(gid) # Important! Set GID first! See comments for details.
    os.setuid(uid)
    os.chroot(chrootpath)
    import untrustworthyplugin
    untrustworthyplugin.run(somevar)
    sys.exit(0)

Это было полезно, и я просто украл этот код, поэтому респект этому парню за достойный пример.

person Logan    schedule 30.07.2012
comment
Это в значительной степени решило мои проблемы. Это было сложно из-за установки psutil в virtualenv и разрешений пользователя, но после некоторых попыток/ошибок я исправил это. - person coya; 27.08.2017
comment
@habnabit, не могли бы вы рассказать о своем редактировании? Я не знаком с тем, почему порядок имеет значение, и я считаю, что он заслуживает более явного указания в ответе, если он имеет последствия для безопасности. - person Logan; 17.10.2017