Как я могу изолировать Python в чистом Python?
Я занимаюсь разработкой веб-игры на чистом Python и хочу, чтобы было доступно несколько простых сценариев для более динамичного игрового контента. Игровой контент может быть добавлен в реальном времени привилегированными пользователями.
Было бы неплохо, если бы языком сценариев был Python. Однако он не может работать с доступом к среде, в которой работает игра, поскольку злонамеренный пользователь может нанести ущерб, что было бы плохо. Можно ли запустить изолированную Python в чистом Python?
Обновление: На самом деле, поскольку истинная поддержка Python была бы излишней, простой язык сценариев с Python-синтаксисом был бы идеальным.
Если нет никаких интерпретаторов сценариев Pythonic, есть ли другие интерпретаторы сценариев с открытым исходным кодом, написанные на чистом Python, которые я мог бы использовать? Требованиями являются поддержка переменных, базовых условий и вызовов функций (не определений).
10 ответов
Это действительно нетривиально.
Есть два способа песочницы Python. Одним из них является создание ограниченной среды (то есть очень мало глобальных и т. Д.) И exec
ваш код внутри этой среды. Это то, что предлагает Месса. Это хорошо, но есть много способов вырваться из песочницы и создать проблемы. Примерно год назад на Python-dev была тема, в которой люди делали что-то от перехвата исключений и поиска внутреннего состояния, чтобы перейти к манипулированию байтовым кодом. Это путь, если вы хотите полный язык.
Другой способ - проанализировать код и затем использовать ast
модуль для удаления ненужных конструкций (например, операторов импорта, вызовов функций и т. д.), а затем для компиляции всего остального. Это путь, если вы хотите использовать Python в качестве языка конфигурации и т. Д.
Другой способ (который может не сработать для вас, поскольку вы используете GAE) - это песочница PyPy. Хотя я сам этим не пользовался, в промежутках говорится, что это единственный настоящий Python в песочнице.
Основываясь на вашем описании требований (требования - это поддержка переменных, базовых условий и вызовов функций (не определений)), вы можете оценить подход 2 и исключить все остальное из кода. Это немного сложно, но выполнимо.
Примерно через десять лет после исходного вопроса Python 3.8.0 поставляется с аудитом. Это может помочь? Для простоты ограничимся записью на жесткий диск - и увидим:
from sys import addaudithook
def block_mischief(event,arg):
if 'WRITE_LOCK' in globals() and ((event=='open' and arg[1]!='r')
or event.split('.')[0] in ['subprocess', 'os', 'shutil', 'winreg']): raise IOError('file write forbidden')
addaudithook(block_mischief)
Уже exec
мог легко записать на диск:
exec("open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')", dict(locals()))
Но мы можем запретить это по желанию, чтобы ни один злой пользователь не мог получить доступ к диску из кода, предоставленного для exec()
. Модули Pythonic, такие какnumpy
или pickle
в конечном итоге использовать доступ к файлам Python, поэтому им также запрещена запись на диск. Вызовы внешних программ также явно отключены.
WRITE_LOCK = True
exec("open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')", dict(locals()))
exec("open('/tmp/FILE','a').write('pwned by l33t h4xx0rz')", dict(locals()))
exec("numpy.savetxt('/tmp/FILE', numpy.eye(3))", dict(locals()))
exec("import subprocess; subprocess.call('echo PWNED >> /tmp/FILE', shell=True)", dict(locals()))
Попытка снятия блокировки изнутри exec()
кажется бесполезным, поскольку одитинговый крючок использует другую копию locals
это недоступно для кода, запущенного exec
. Пожалуйста, докажите, что я ошибаюсь.
exec("print('muhehehe'); del WRITE_LOCK; open('/tmp/FILE','w')", dict(locals()))
...
OSError: file write forbidden
Конечно, код верхнего уровня может снова включить файловый ввод-вывод.
del WRITE_LOCK
exec("open('/tmp/FILE','w')", dict(locals()))
Песочница в Cpython оказалась чрезвычайно сложной, и многие предыдущие попытки потерпели неудачу. Этот подход также не совсем безопасен, например, для общедоступного веб-доступа:
возможно, гипотетические скомпилированные модули, использующие прямые вызовы ОС, не могут быть проверены Cpython - рекомендуется занести безопасные чистые питонические модули в белый список.
Определенно все еще существует возможность сбоя или перегрузки интерпретатора Cpython.
Может быть, еще остались лазейки для записи файлов на жесткий диск. Но я не мог использовать какие-либо обычные уловки с обходом песочницы для записи одного байта. Мы можем сказать, что "поверхность атаки" экосистемы Python сводится к довольно узкому списку событий, которые должны быть (запрещены) разрешены: https://docs.python.org/3/library/audit_events.html
Буду благодарен любому, кто укажет мне на недостатки этого подхода.
РЕДАКТИРОВАТЬ: Так что это тоже небезопасно! Я очень благодарен @Emu за его умный взлом с использованием перехвата исключений и самоанализа:
#!/usr/bin/python3.8
from sys import addaudithook
def block_mischief(event,arg):
if 'WRITE_LOCK' in globals() and ((event=='open' and arg[1]!='r') or event.split('.')[0] in ['subprocess', 'os', 'shutil', 'winreg']):
raise IOError('file write forbidden')
addaudithook(block_mischief)
WRITE_LOCK = True
exec("""
import sys
def r(a, b):
try:
raise Exception()
except:
del sys.exc_info()[2].tb_frame.f_back.f_globals['WRITE_LOCK']
import sys
w = type('evil',(object,),{'__ne__':r})()
sys.audit('open', None, w)
open('/tmp/FILE','w').write('pwned by l33t h4xx0rz')""", dict(locals()))
Я предполагаю, что аудит+подпроцессинг - это путь, но не используйте его на производственных машинах:
https://bitbucket.org/fdominec/experimental_sandbox_in_cpython38/src/master/sandbox_experiment.py
AFAIK можно запустить код в полностью изолированной среде:
exec somePythonCode in {'__builtins__': {}}, {}
Но в такой среде вы почти ничего не можете сделать:) (вы даже не можете import
модуль; но все же злонамеренный пользователь может запустить бесконечную рекурсию или вызвать нехватку памяти.) Возможно, вы захотите добавить некоторые модули, которые будут интерфейсом к вашему игровому движку.
Я не уверен, почему никто не упоминает об этом, но в Zope 2 есть вещь, называемая Python Script, а именно - ограниченный Python, выполняемый в песочнице, без какого-либо доступа к файловой системе, с доступом к другим объектам Zope, контролируемым механизмами безопасности Zope, с импортом, ограниченным безопасным подмножеством.
Zope в целом довольно безопасен, поэтому я думаю, что нет никаких известных или очевидных способов вырваться из песочницы.
Я не уверен, как именно реализованы скрипты Python, но эта функция была примерно с 2000 года.
И вот магия, стоящая за PythonScripts, с подробной документацией: http://pypi.python.org/pypi/RestrictedPython - он даже выглядит так, как будто не имеет никаких зависимостей от Zope, поэтому может использоваться автономно.
Обратите внимание, что это не для безопасного выполнения произвольного кода Python (большинство случайных сценариев завершится ошибкой при первом импорте или доступе к файлу), а скорее для использования Python для ограниченного использования сценариев в приложении Python.
Этот ответ от моего комментария до вопроса, закрытого как дубликат этого: Python от Python: ограничение функциональности?
Я хотел бы посмотреть на двухсерверный подход. Первый сервер - это привилегированный веб-сервер, на котором живет ваш код. Второй сервер является очень жестко контролируемым сервером, который предоставляет только веб-службу или службу RPC и выполняет недоверенный код. Вы предоставляете создателю контента свой пользовательский интерфейс. Например, если вы разрешили конечному пользователю создавать элементы, вы бы посмотрели, что вызвало сервер с кодом для выполнения и набором параметров.
Вот и абстрактный пример лечебного зелья.
{function_id='healing potion', action='use', target='self', inventory_id='1234'}
Ответ может быть что-то вроде
{hp='+5' action={destroy_inventory_item, inventory_id='1234'}}
Хм. Это мысленный эксперимент, я не знаю, как это делается:
Вы могли бы использовать compiler
пакет для parse
сценарий. Затем вы можете пройти по этому дереву, добавив префиксы ко всем идентификаторам - переменным, именам методов и т. Д. has|get|setattr
вызовы и т. д.) - с уникальной преамбулой, чтобы они не могли ссылаться на ваши переменные. Вы также можете убедиться, что compiler
Сам пакет не был вызван, и, возможно, другие занесенные в черный список вещи, такие как открытие файлов. Затем вы выдаете код Python для этого, и compiler.compile
Это.
Документы отмечают, что compiler
Пакет отсутствует в Python 3.0, но не упоминает, что такое альтернатива 3.0.
В целом, это параллельно с тем, как программное обеспечение форума и тому подобное пытаются внести в белый список "безопасный" Javascript, HTML и т. Д. И у них исторически сложилась плохая репутация топать все побеги. Но вам может быть больше удачи с Python:)
Возможно, вам будет интересен раздел языковых служб Python в libref для написания вашего собственного парсера.
На этой вики-странице вы найдете некоторые идеи, но, похоже, это не так просто сделать.
Я думаю, что ваша лучшая ставка будет комбинацией ответов до сих пор.
Вы захотите проанализировать и дезинфицировать входные данные - например, удалить любые операторы импорта.
Затем вы можете использовать exec- пример Messa (или что-то подобное), чтобы разрешить выполнение кода только для встроенных переменных по вашему выбору - скорее всего, какой-то определенный вами API, который предоставляет программисту доступ к функциям, которые вы считаете уместными.
@community wiki сказал:
РЕДАКТИРОВАТЬ: Так что это тоже небезопасно! Я очень благодарен @Emu за его умный взлом с использованием перехвата исключений и самоанализа:
Он мог бы использовать
from sys import exit
exit(9)