запретить доступ к файловой системе внутри exec и eval в Python - PullRequest
4 голосов
/ 24 февраля 2011

Я хочу запретить доступ к файловой системе из кода клиента, поэтому я думаю, что могу перезаписать функцию открытия

env = {
   'open': lambda *a: StringIO("you can't use open")
}

exec(open('user_code.py'), env)

, но я получил это

unqualified exec is not allowed in function 'my function' it contains a 
nested function with free variables

Я также пытаюсь

 def open_exception(*a):
     raise Exception("you can't use open")
 env = {
     'open': open_exception
 }

, но получил то же исключение (не "вы не можете использовать open")

Я хочу предотвратить:

выполнение этого:

"""def foo():
     return open('some_file').read()
print foo()"""

и оцените это

"open('some_file').write('some text')"

Я также использую сеанс для хранения кода, который был оценен ранее, поэтому мне нужно предотвратить выполнение этого:

"""def foo(s):
   return open(s)"""

и затем оценить это

"foo('some').write('some text')"

Я не могу использовать регулярные выражения, потому что кто-то может использовать (eval внутри строки)

"eval(\"opxx('some file').write('some text')\".replace('xx', 'en')"

Есть ли какой-нибудь способ запретить доступ к файловой системе внутри exec / eval?(Мне нужны оба)

Ответы [ 5 ]

11 голосов
/ 24 февраля 2011

Нет способа запретить доступ к файловой системе внутри exec / eval. Вот пример кода, который демонстрирует, как пользовательский код может вызывать классы с ограниченным доступом, которые всегда работают:

import subprocess
code = """[x for x in ().__class__.__bases__[0].__subclasses__() 
           if x.__name__ == 'Popen'][0](['ls', '-la']).wait()"""
# Executing the `code` will always run `ls`...
exec code in dict(__builtins__=None)

И не думайте о фильтрации входных данных, особенно с помощью регулярных выражений.

Вы можете рассмотреть несколько альтернатив:

  1. ast.literal_eval если бы вы могли ограничиться только простыми выражениями
  2. Использование другого языка для кода пользователя. Вы можете посмотреть на Lua или JavaScript - оба они иногда используются для запуска небезопасного кода внутри песочниц.
  3. Это проект pysandbox , хотя я не могу гарантировать, что код в песочнице действительно безопасен. Python не был разработан для песочницы, и, в частности, реализация CPython не была написана с учетом песочницы. Даже автор , похоже, сомневается в возможности безопасной реализации такой песочницы.
5 голосов
/ 10 мая 2012

На самом деле можно сделать.

То есть практически то, что вы описываете, может быть выполнено в Linux, в отличие от других ответов здесь.Таким образом, вы можете выполнить настройку, при которой у вас может быть exec -подобный вызов, который запускает ненадежный код в режиме безопасности, в который довольно сложно проникнуть, и который позволяет выводить результат.Ненадежному коду вообще не разрешен доступ к файловой системе, кроме чтения специально разрешенных частей Python vm и стандартной библиотеки.

Если это достаточно близко к тому, что вы хотели, читайте дальше.

IЯ представляю себе систему, в которой ваша exec-подобная функция порождает подпроцесс под очень строгим профилем AppArmor, например, используемый Straitjacket (см. здесь и здесь ).Это ограничит доступ ко всей файловой системе на уровне ядра, кроме файлов, специально разрешенных для чтения.Это также ограничит размер стека процесса, максимальный размер сегмента данных, максимальный размер резидентного набора, время ЦП, количество сигналов, которые могут быть поставлены в очередь, и размер адресного пространства.У процесса будут заблокированы память, ядра, блокировки flock / fcntl, очереди сообщений POSIX и т. Д.Если вы хотите разрешить использование временных файлов с ограниченным размером в пустой области, вы можете mkstemp сделать это и сделать его доступным для подпроцесса, а также разрешить запись туда при определенных условиях (убедитесь, что жесткие ссылки абсолютно запрещены).Вы должны быть уверены, что удалили что-нибудь интересное из среды подпроцесса и поместили его в новую сессию и группу процессов и закрыли все FD в подпроцессе, кроме stdin / stdout / stderr, если вы хотите разрешить связь сте.

Если вы хотите иметь возможность получить объект Python обратно из ненадежного кода, вы можете обернуть его во что-то, что выводит repr результата в стандартный вывод, и после проверки его размера выоцените это с ast.literal_eval().Это довольно сильно ограничивает возможные типы объектов, которые могут быть возвращены, но на самом деле, что-либо более сложное, чем эти базовые типы, вероятно, несет в себе возможность злонамеренного секрита, предназначенного для запуска в вашем процессе.Ни при каких обстоятельствах вы не должны использовать pickle для протокола связи между процессами.

5 голосов
/ 24 февраля 2011

Вы не можете превратить exec() и eval() в безопасную песочницу. Вы всегда можете получить доступ к встроенному модулю, если доступен модуль sys ::

sys.modules[().__class__.__bases__[0].__module__].open

И даже если sys недоступен, вы все равно можете получить доступ к любому классу нового стиля, определенному в любом импортированном модуле, практически таким же образом. Это включает в себя все классы ввода-вывода в io.

3 голосов
/ 24 февраля 2011

Поскольку @Brian предлагает переопределение open, не работает:

def raise_exception(*a):
    raise Exception("you can't use open")

open = raise_exception

print eval("open('test.py').read()", {})

это отображает содержимое файла, но это (объединение ответов @Brian и @lunaryorn)

import sys
def raise_exception(*a):
    raise Exception("you can't use open")

__open = sys.modules['__builtin__'].open
sys.modules['__builtin__'].open = raise_exception

print eval("open('test.py').read()", {})

сгенерирует это:

Traceback (most recent call last):
  File "./test.py", line 11, in <module>
    print eval("open('test.py').read()", {})
  File "<string>", line 1, in <module>
  File "./test.py", line 5, in raise_exception
    raise Exception("you can't use open")
Exception: you can't use open
Error in sys.excepthook:
Traceback (most recent call last):
  File "/usr/lib/python2.6/dist-packages/apport_python_hook.py", line 48, in apport_excepthook
    if not enabled():
  File "/usr/lib/python2.6/dist-packages/apport_python_hook.py", line 23, in enabled
    conf = open(CONFIG).read()
  File "./test.py", line 5, in raise_exception
    raise Exception("you can't use open")
Exception: you can't use open

Original exception was:
Traceback (most recent call last):
  File "./test.py", line 11, in <module>
    print eval("open('test.py').read()", {})
  File "<string>", line 1, in <module>
  File "./test.py", line 5, in raise_exception
    raise Exception("you can't use open")
Exception: you can't use open

, и вы можете получить доступ к open вне кода пользователя через __open

1 голос
/ 24 февраля 2011

«Вложенная функция» относится к тому факту, что она объявлена ​​внутри другой функции, а не к лямбде.Объявите ваше open переопределение на верхнем уровне вашего модуля, и оно должно работать так, как вы хотите.

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

...