Eval python: Динамическое выполнение выражений в Python: функция eval()
Динамическое выполнение выражений в Python: функция eval()
Функция eval()
полезна, когда необходимо выполнить динамически обновляемое выражение Python из какого-либо ввода (например, функции input()
), представленного в виде строки или объекта байт-кода. Это невероятно полезный инструмент, но то, что она может выполнять программный код, имеет важные последствия для безопасности, которые следует учесть перед ее применением.
Статья является сокращенным переводом публикации Леоданиса Посо Рамоса Python eval(): Evaluate Expressions Dynamically. Из этого руководства вы узнаете:
- Как работает
eval()
. - Как использовать
eval()
для динамического выполнения кода. - Как минимизировать риски для безопасности, связанные с использованием
eval()
.
Вы можете использовать встроеннyю функцию eval()
для динамического исполнения выражений из ввода на основе строки или скомпилированного кода. Если вы передаете в eval()
строку, то функция анализирует ее, компилирует в байт-код и выполняет как выражение Python.
Сигнатура eval()
определена следующим образом:
eval(expression[, globals[, locals]])
Первый аргумент expression
содержит выражение, которое необходимо выполнить. Функция также принимает два необязательных аргумента globals
и locals
, о которых мы поговорим в соответствующих разделах. Начнём по порядку – с аргумента expression
.
Примечание
Для динамического выполнения кода можно также использовать функцию exec()
. Основное различие между eval()
и exec()
состоит в том, что eval()
может выполнять лишь выражения, тогда как функции exec()
можно «скормить» любой фрагмент кода Python.
Когда мы вызываем eval()
, содержание expression
воспринимается интерпретатором как выражение Python. Посмотрите на следующие примеры, принимающие строковый ввод:
>>> eval("2 ** 8")
256
>>> eval("1024 + 1024")
2048
>>> eval("sum([8, 16, 32])")
56
>>> x = 100
>>> eval("x * 2")
200
При вызове eval()
со строковым выражением в качестве аргумента, функция возвращает значение, полученное в результате оценки входной строки. По умолчанию eval()
имеет доступ к глобальным именам, таким как x
в приведенном выше примере.
Чтобы оценить строковое выражение, eval()
выполняет следующую последовательность действий:
- Парсинг выражения.
- Компилирование в байт-код.
- Выполнение кода выражения Python.
- Возвращение результата.
Имя аргумента expression
подчеркивает, что функция работает только с выражениями, но не составными конструкциями. При попытке передачи блока кода вместо выражения будет получено исключение SyntaxError
:
>>> x = 100
>>> eval("if x: print(x)")
SyntaxError: invalid syntax
Таким образом, в eval()
нельзя передать конструкции c if
, import
, def
или class
, с циклами for
и while
. Однако ключевое слово for
может использоваться в eval()
в случае выражений для генераторов списков.
В eval()
запрещены и операции присваивания:
>>> eval("pi = 3.1416")
SyntaxError: invalid syntax
SyntaxError
также вызывается в случаях, когда eval()
не удается распарсить выражение из-за ошибки в записи:
>>> eval("5 + 7 *")
SyntaxError: unexpected EOF while parsing
В eval()
можно передавать объекты кода (code objects). Чтобы скомпилировать код, который вы собираетесь передать eval()
, можно использовать compile()
. Это встроенная функция, которая может компилировать строку в объект кода или AST-объект.
Детали того, как использовать compile()
, выходят за рамки этого руководства, но здесь мы кратко рассмотрим первые три обязательных аргумента:
source
содержит исходный код, который необходимо скомпилировать. Этот аргумент принимает обычные строки, байтовые строки и объекты AST.filename
определяет файл, из которого прочитан код. Если используется строчный ввод, значение аргумента должно быть равно строке<string>
.mode
указывает, какой тип объекта кода мы хотим получить. Если нужно обработать код с помощьюeval()
, в качестве значения аргумента указывается"eval"
Таким образом, мы можем использовать compile()
для предоставления объектов кода вeval()
вместо обычных строк:
>>> code = compile("5 + 4", "<string>", "eval")
>>> eval(code)
9
>>> import math
>>> code = compile("4 / 3 * math.pi * math.pow(25, 3)", "<string>", "eval")
>>> eval(code)
65449.84694978735
Использование объектов кода полезно при многократном вызове. Если мы предварительно скомпилируем входное выражение, то последующие вызовы eval()
будут выполняться быстрее, так как не будут повторяться шаги синтаксического анализа и компиляции.
Аргумент globals
опционален. Он содержит словарь, обеспечивающий доступ eval()
к глобальному пространству имен. С помощью глобальных переменных можно указать eval()
, какие глобальные имена использовать при выполнении выражения.
Глобальные имена – это все те имена, которые доступны в текущей глобальной области или пространстве имен. Вы можете получить к ним доступ из любого места в вашем коде.
Все имена, переданные глобальным переменным в словаре, будут доступны eval()
во время выполнения.
>>> x = 100 # Глобальная переменная
>>> eval("x + 100", {"x": x})
200
>>> y = 200 # Другая глобальная переменная
>>> eval("x + y", {"x": x})
NameError: name 'y' is not defined
Любые глобальные имена, определенные вне пользовательского словаря globals
, не будут доступны изнутри eval()
, будет вызвано исключение NameError
.
>>> eval("x + y", {"x": x, "y": y})
300
Вы также можете указать имена, которых нет в текущей глобальной области видимости. Чтобы это работало, нужно указать конкретное значение для каждого имени. Тогда eval()
будет интерпретировать эти имена, как если бы это были глобальные переменные:
>>> eval("x + y + z", {"x": x, "y": y, "z": 300})
600
>>> z # самой переменной нет в глобальной области видимости
NameError: name 'z' is not defined
Если вы предоставите eval()
пользовательский словарь, который не содержит значения для ключа "__builtins__"
, то ссылка на словарь встроенных функций всё равно будет автоматически добавлена к ключу "__builtins__"
, прежде чем выражение будет проанализировано. Это гарантирует, что eval()
имеет полный доступ ко всем встроенным именам Python при оценке выражения.
>>> eval("sum([2, 2, 2])", {})
6
>>> eval("min([1, 2, 3])", {})
1
>>> eval("pow(10, 2)", {})
100
Несмотря на переданный пустой словарь ({}
), eval()
имеет доступ к встроенным функциям.
При вызове eval()
без передачи пользовательского словаря в глобальные переменные аргумент по умолчанию будет использовать словарь, возвращаемый globals()
в среде, где вызывается eval()
:
>>> x = 100 # Глобальная переменная
>>> y = 200 # Другая глобальная переменная
>>> eval("x + y")
300
Таким образом, передача словаря в аргументе globals
служит как способ намеренно ограничить область видимость имен для функции eval()
.
Аргумент locals
также является необязательным аргументом. В этом случае словарь содержит переменные, которые eval()
использует в качестве локальных имен при оценке выражения.
Локальными называются те имена (переменные, функции, классы и т.д.), которые мы определяем внутри данной функции. Локальные имена видны только изнутри включающей функции.
>>> eval("x + 100", {}, {"x": 100})
200
>>> eval("x + y", {}, {"x": 100})
NameError: name 'y' is not defined
Обратите внимание, что для передачи словаря locals
сначала необходимо предоставить словарь для globals
. Передача по ключу в случае eval()
не работает:
>>> eval("x + 100", locals={"x": 100})
TypeError: eval() takes no keyword arguments
Главное практическое различие между globals
и locals
заключается в том, что Python автоматически вставит ключ "__builtins__"
в globals
, если этот ключ еще не существует. Cловарь locals
остается неизменным во время выполнения eval()
.
Функция eval()
используется, когда нужно динамически изменять выражения, а применение других техник и инструментов Python требует избыточных усилий. В этом разделе мы обсудим, как использовать eval()
для булевых, математических и прочих выражений Python.
Булевы выражения
Булевы выражения – это выражения Python, которые возвращают логическое значение. Обычно они используются для проверки, является ли какое-либо условие истинным или ложным:
>>> x = 100
>>> y = 100
>>> eval("x != y")
False
>>> eval("x < 200 and y > 100")
False
>>> eval("x is y")
True
>>> eval("x in {50, 100, 150, 200}")
True
Зачем же может потребоваться использовать eval()
вместо непосредственного применения логического выражения? Предположим, нам нужно реализовать условный оператор, но вы хотите на лету менять условие:
def func(a, b, condition):
if eval(condition):
return a + b
return a - b
>>> func(2, 4, "a > b")
-2
>>> func(2, 4, "a < b")
6
>>> func(2, 2, "a is b")
4
Внутри func()
для оценки предоставленного условия используется функция eval()
, возвращающая a+b
или a-b
в соответствии с результатом оценки.
Теперь представьте, как бы вы реализовали то же поведение без eval()
для обработки любого логического выражения.
Математические выражения
Один из распространенных вариантов использования eval()
в Python – оценка математических выражений из строкового ввода. Например, если вы хотите создать калькулятор на Python, вы можете использовать eval()
, чтобы оценить вводимые пользователем данные и вернуть результат вычислений:
>>> eval("5 + 7")
12
>>> eval("(5 + 7) / 2")
6.0
>>> import math
>>> eval("math.sqrt(math.pow(10, 2) + math.pow(15, 2))")
18.027756377319946
Выражения общего вида
Вы можете использовать eval()
и с более сложными выражениями Python, включающими вызовы функций, создание объектов, доступ к атрибутам и т. д.
Например, можно вызвать встроенную функцию или функцию, импортированную с помощью стандартного или стороннего модуля. В следующих примерах eval()
используется для запуска различных системных команд.
>>> import subprocess
>>> # Запуск команды echo
>>> eval("subprocess.getoutput('echo Hello, World')")
'Hello, World'
>>> # Запуск Firefox (если он установлен)
>>> eval("subprocess.getoutput('firefox')")
Таким образом, можно передавать команды через какой-либо строковый интерфейс (например, форму в браузере) и выполнять код Python.
Однако по той же причине eval()
может подвергнуть нас серьезным угрозам безопасности, например, позволит злоумышленнику запускать системные команды или выполнять кода на нашем компьютере. В следующем разделе мы обсудим способы устранения угроз безопасности, связанных с eval()
.
В связи с проблемами безопасности обычно рекомендуется по возможности не использовать eval()
. Но если вы решили, что функция необходима, простое практическое правило состоит в том, чтобы никогда не использовать ее для любого ввода, которому нельзя доверять. Сложность в том, чтобы выяснить, каким видам ввода доверять можно .
В качестве примера того, как безответственное использование eval()
может сделать ваш код небезопасным, предположим, что вы хотите создать онлайн-сервис для оценки произвольных выражений Python. Ваш пользователь вводит выражения и нажимает кнопку «Выполнить». Приложение получает пользовательский ввод и передает его для выполнения в eval()
.
Если вы используете Linux и приложения имеет необходимые разрешения, то злонамеренный пользователь может ввести опасную строку, подобную следующей:
"__import__('subprocess').getoutput('rm –rf *')"
Выполнение выражения удалит все файлы в текущей директории.
Примечание
__import__()
– это встроенная функция, которая принимает имя модуля в виде строки и возвращает ссылку на объект модуля. __import__()
– это функция, которая полностью отличается от оператора import
. Как мы упоминали выше, вы не можете вызвать оператор импорта с помощью eval()
.
Ограничение globals и locals
Вы можете ограничить среду выполнения eval()
, задавая собственные словари аргументам globals
и locals
. Например, пустые словари для обоих аргументов, чтобы eval()
не мог получить доступ к именам в текущей области или пространстве имен вызывающей стороны:
>>> x = 100
>>> eval("x * 5", {}, {})
NameError: name 'x' is not defined
К сожалению, это ограничение не устраняет другие проблемы безопасности, связанные с использованием eval()
, поскольку остается доступ ко всем встроенным именам и функциям Python.
Ограничение __builtins__
Как мы видели ранее, перед синтаксическим анализом выражения eval()
автоматически вставляет ссылку на словарь __builitins__
в globals
. Злоумышленник может использовать это поведение, используя встроенную функцию __import__()
, чтобы получить доступ к стандартной библиотеке или любому стороннему модулю, установленному в системе:
>>> eval("__import__('math').sqrt(25)", {}, {})
5.0
>>> eval("__import__('subprocess').getoutput('echo Hello, World')", {}, {})
'Hello, World'
Чтобы минимизировать риски, можно переопределить __builtins__
в globals
:
>>> eval("__import__('math').sqrt(25)", {"__builtins__": {}}, {})
NameError: name '__import__' is not defined
Ограничение имён во входных данных
Однако даже после таких ухищрений Python останется уязвим. Например, можно получить доступ к объекту класса, используя литерал типа, например ""
, []
, {}
или ()
, а также некоторые специальные атрибуты:
>>> "".__class__.__base__
object
>>> [].__class__.__base__
object
Получив доступ к объекту, можно использовать специальный метод .__subclasses__()
, чтобы получить доступ ко всем классам, которые наследованы объектом. Вот как это работает:
for sub_class in ().__class__.__base__.__subclasses__():
print(sub_class.__name__)
type
weakref
weakcallableproxy
weakproxy
int
bytearray
bytes
list
...
Этот код напечатает большой список классов. Некоторые из этих классов довольно мощные и могут быть чрезвычайно опасны в чужих руках. Это открывает еще одну важную дыру в безопасности, которую вы не сможете закрыть, просто ограничивая окружение eval()
:
input_string = """[
c for c in ().__class__.__base__.__subclasses__()
if c.__name__ == "range"
][0](10)"""
>>> list(eval(input_string, {"__builtins__": {}}, {}))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Генератор списка в приведенном коде фильтрует классы объекта, чтобы вернуть список, содержащий класс range
. Далее range
вызывается для создания соответствующего объекта. Это хитрый способ обойти исключение TypeError
, вызываемое в результате ограничения "__builtins__"
.
>>> list(eval(range(10), {"__builtins__": {}}, {}))
TypeError: eval() arg 1 must be a string, bytes or code object
Возможное решение этой уязвимости состоит в том, чтобы ограничить использование имен во входных данных набором безопасных имен либо исключить всякое использование любых имен.
Чтобы реализовать эту технику, необходимо выполнить следующие шаги:
- Создать словарь, содержащий имена, которые могут использоваться в
eval()
. - Скомпилировать входную строку в байт-код, используя
compile()
в режимеeval
. - Проверить
.co_names
в объекте байт-кода, чтобы убедиться, что он содержит только разрешенные имена. - Вызвать исключение
NameError
, если пользователь пытается использовать недопустимое имя.
Взглянем на следующую функцию, в которой реализованы все эти шаги:
def eval_expression(input_string):
allowed_names = {"sum": sum}
code = compile(input_string, "<string>", "eval")
for name in code.co_names:
if name not in allowed_names:
raise NameError(f"Использование {name} не разрешено.")
return eval(code, {"__builtins__": {}}, allowed_names)
Эта функция ограничивает имена, которые можно использовать в eval()
, именами в словаре allowed_names
. Для этого функция использует .co_names
– атрибут объекта кода, содержащий кортеж имен в объекте кода.
Следующие примеры показывают, как написанная нами функция eval_expression()
работает на практике:
>>> eval_expression("3 + 4 * 5 + 25 / 2")
35.5
>>> eval_expression("sum([1, 2, 3])")
6
>>> eval_expression("pow(10, 2)")
NameError: Использование pow не разрешено.
Если нужно полностью запретить применение имен, достаточно переписать eval_expression()
следующим образом:
def eval_expression(input_string):
code = compile(input_string, "<string>", "eval")
if code.co_names:
raise NameError(f"Использование имён запрещено.")
return eval(code, {"__builtins__": {}}, {})
>>> eval_expression("3 + 4 * 5 + 25 / 2")
35.5
>>> eval_expression("sum([1, 2, 3])")
NameError: Использование имён запрещено.
Ограничение входных данных до литералов
Типичный пример использования eval()
в Python – это выполнение выражений, содержащих стандартные литералы Python. Задача настолько распространенная, что стандартная библиотека предоставляет соответствующую функцию literal_eval()
. Функция не поддерживает операторы, но работает со списками, кортежами, числами, строками и т. д.:
>>> from ast import literal_eval
>>> literal_eval("15.02")
15.02
>>> literal_eval("[1, 15]")
[1, 15]
>>> literal_eval("{'one': 1, 'two': 2}")
{'one': 1, 'two': 2}
>>> literal_eval("sum([1, 15]) + 5 + 8 * 2")
ValueError: malformed node or string: ...
Использование eval() совместно с input()
В Python 3.x встроенная функция input()
читает пользовательский ввод из командной строки, преобразует его в строку, удаляет завершающий символ новой строки и возвращает результат вызывающей стороне. Поскольку результатом input()
является строка, ее можно передать в eval()
и выполнить как выражение Python:
>>> eval(input("Введите математическое выражение: "))
Введите математическое выражение: 15*2
30
Это распространенный вариант использования eval()
. Он также эмулирует поведеие input()
в версиях Python 2.x, где функции можно было передать строковое выражение для выполнения (впоследствии от этого отказались из соображений безопасности).
Построим обработчик математических выражений
Итак, мы узнали, как работает eval()
в Python и как использовать функцию на практике. Мы также выяснили, что eval()
имеет важные последствия для безопасности и что обычно считается хорошей практикой избегать использования eval()
в коде. Однако в некоторых ситуациях eval()
может сэкономить много времени и усилий.
В этом разделе вы напишем приложение для оценки математических выражений на лету. Без использования eval()
, нам потребовалось бы выполнить следующие шаги:
- Распарсить входное выражение.
- Преобразовать компоненты выражения в объекты Python (числа, операторы, функции).
- Объединить всё в исполняемое выражение.
- Проверить валидность выражения для Python.
- Выполнить итоговое выражение и вернуть результат вычислений.
Это потребовало бы большой работы, учитывая разнообразие возможных выражений, которые Python может обрабатывать. К счастью, теперь мы знаем о функции eval()
.
Всё приложение будет храниться в скрипте mathrepl.py
. Постепенно мы его заполним необходимым содержимым. Начнем со следующего кода:
mathrepl.py
import math
__version__ = "1.0"
ALLOWED_NAMES = {
k: v for k, v in math.__dict__.items() if not k.startswith("__")
}
PS1 = "mr>>"
WELCOME = f"""
MathREPL {__version__} - обработчик математических выражений на Python!
Введите математическое выражение после приглашения "{PS1}".
Для дополнительной информации используйте команду help.
Чтобы выйти, наберите quit или exit.
"""
USAGE = f"""
Соберите математическое выражение из чисел и операторов.
Можно использовать любые из следующих функций и констант:
{', '.join(ALLOWED_NAMES.keys())}
"""
Модуль math
мы используем для того, чтобы определить все доступные имена. Три строковые константы применяются для вывода строк в интерфейсе программы. Напишем ключевую функцию нашей программы:
mathrepl.py
def evaluate(expression):
"""Вычисляет математическое выражение."""
# Компиляция выражения в байт-код
code = compile(expression, "<string>", "eval")
# Валидация доступных имен
for name in code.co_names:
if name not in ALLOWED_NAMES:
raise NameError(f"The use of '{name}' is not allowed")
return eval(code, {"__builtins__": {}}, ALLOWED_NAMES)
Осталось лишь написать код для взаимодействия с пользователем. В функции main()
мы определяем основной цикл программы для чтения введенных данных и расчета математических выражений, введенных пользователем:
mathrepl.py
def main():
"""Читает и рассчитывает введенное выражение"""
print(WELCOME)
while True:
# Читаем пользовательский ввод
try:
expression = input(f"{PS1} ")
except (KeyboardInterrupt, EOFError):
raise SystemExit()
# Поддержка специальных команд
if expression.lower() == "help":
print(USAGE)
continue
if expression.lower() in {"quit", "exit"}:
raise SystemExit()
# Вычисление выражения и обработка ошибок
try:
result = evaluate(expression)
except SyntaxError:
# Некорректное выражение
print("Вы ввели некорректное выражение.")
continue
except (NameError, ValueError) as err:
# Если пользователь попытался использовать неразрешенное имя
# или неверное значение в переданной функции
print(err)
continue
# Выводим результат, если не было ошибок
print(f"Результат: {result}")
Проверим результат нашей работы:
Shell
python3 mathrepl.py
MathREPL 1.0 - обработчик математических выражений на Python!
Введите математическое выражение после приглашения "mr>>".
Для дополнительной информации используйте команду help.
Чтобы выйти, наберите quit или exit.
mr>> 25 * 2
Результат: 50
mr>> sqrt(25)
Результат: 5.0
mr>> pi
Результат: 3.141592653589793
mr>> 5 * (25 + 4
Вы ввели некорректное выражение.
mr>> sum([1, 2, 3, 4, 5])
The use of 'sum' is not allowed
mr>> sqrt(-15)
math domain error
mr>> factorial(-15)
factorial() not defined for negative values
mr>> exit
Вот и всё – наш обработчик математических выражений готов! В случае ошибок при вводе или математически некорректных выражений мы получаем необходимое пояснение. Для самой обработки введенных данных потребовалось лишь несколько строк и функция eval()
.
Итак, вы можете использовать eval()
для выполнения выражений Python из строкового или кодового ввода. Эта встроенная функция полезна, когда вы пытаетесь динамически обновлять выражения Python и хотите избежать проблем с созданием собственного обработчика выражений. Однако пользоваться ей стоит с осторожностью.
***
Другие наши недавние статьи с подробным разбором различных аспектов стандартной библиотеки Python:
Что делает eval () Python?
много хороших ответов здесь, но ни один не описывает использование eval
в контексте globals=
и locals=
kwargs. Они могут использоваться для ограничения методов, доступных через eval
метод. Например, если вы загружаете новый интерпретатор python, то locals()
и globals()
будет то же самое и выглядеть примерно так:
>>> globals()
{'__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__doc__': None,
'__spec__': None, '__builtins__': <module 'builtins' (built-in)>,
'__package__': None, '__name__': '__main__'}
есть, конечно, методы в рамках builtins
модуль, который может нанести значительный ущерб системе. Но возможно заблокируйте все, что нам не нужно. Возьмем пример. Скажем, мы хотим построить список для представления домена доступных ядер в системе. Для меня у меня есть 8 ядер, поэтому мне нужен список [1, 8]
.
>>>from os import cpu_count()
>>>eval('[1, cpu_count()'])
[1, 8]
дополнительно все __builtins__
доступно.
>>>eval('abs(-1)')
1
Ok. Таким образом, мы видим один метод, который мы хотим разоблачить, и пример одного (из многих, которые могут быть гораздо более коварными) метода, который мы не хотим разоблачать. Так давайте блокировать всё.
>>>eval('[1, cpu_count()]', {'__builtins__':None}, {})
TypeError: 'NoneType' object is not subscriptable
мы эффективно заблокировали все __builtins__
методы и как таковые принесли уровень защиты в нашу систему. На этом этапе мы можем начать добавлять обратно методы,которые мы хотим выставить.
>>>from os import cpu_count
>>>exposed_methods = {'cpu_count': cpu_count}
>>>eval('cpu_count()', {'__builtins__':None}, exposed_methods)
8
>>>eval('abs(cpu_count())', {'__builtins__':None}, exposed_methods)
TypeError: 'NoneType' object is not subscriptable
теперь у нас есть метод cpu_count доступен, все еще блокируя все, что мы не хотим. На мой взгляд, это супер мощный и явно из области других ответов не общая реализация. Есть множество применений для что-то вроде этого и до тех пор, пока оно обрабатывается правильно, я лично чувствую eval
можно безопасно использовать к большому значению.
Б. Н.
что-то еще, что круто об этих kwargs
это то, что вы можете начать использовать стенографию для своего кода. Предположим, вы используете eval как часть конвейера для выполнения некоторого импортированного текста. Текст не должен иметь точный код, он может следовать формату файла шаблона и выполнять все, что вы хотите. Для пример:
>>>from os import cpu_count
>>>eval('[1,cores]', {'__builtins__': None}, {'cores': cpu_count()})
[1, 8]
Eval в Python | Портал информатики для гиков
В этой статье обсуждается встроенная функция в Python, eval .
Это интересная хакерская утилита в Python, которая позволяет программе Python запускать код Python внутри себя.
Метод eval () анализирует переданное ему выражение и запускает выражение (код) Python в программе.
Синтаксис eval :
eval(expression, globals=None, locals=None)
- выражение: эта строка анализируется и оценивается как выражение Python
- глобальные (необязательно): словарь для указания доступных глобальных методов и переменных.
- locals (необязательно): другой словарь для указания доступных локальных методов и переменных.
Давайте рассмотрим это с помощью простой программы на Python:
|
function_creator — это функция, которая оценивает математические функции, созданные пользователем.
Рассмотрим вывод:
Enter the function(in terms of x):x*(x+1)*(x+2) Enter the value of x:3 y = 60
Давайте немного проанализируем код:
- Вышеуказанная функция принимает любое выражение в переменной x в качестве входных данных.
- Затем пользователь должен ввести значение х .
- Наконец, мы оцениваем выражение python, используя встроенную функцию eval () , передавая expr в качестве аргумента.
Проблемы с уязвимостью в eval
Наша текущая версия function_creator имеет несколько уязвимостей.
Пользователь может легко раскрыть скрытые значения в программе или вызвать опасную функцию, так как eval выполнит все, что ему передано.
Например, если вы вводите так:
Enter the function(in terms of x):secret_function()
Enter the value of x:0
Вы получите вывод:
y = Secret key is 1234
Также рассмотрим ситуацию, когда вы импортировали модуль os в вашу программу на python. Модуль os предоставляет переносимый способ использования функций операционной системы, таких как: чтение или запись файла. Одна команда может удалить все файлы в вашей системе!
Конечно, в большинстве случаев (например, настольные программы) пользователь не может сделать больше, чем он мог бы, написав свой собственный скрипт Python, но в некоторых приложениях (например, веб-приложения, компьютеры с киосками) это может быть риском!
Решение состоит в том, чтобы ограничить eval только теми функциями и переменными, которые мы хотим сделать доступными.
Сделать eval безопасным
Функция eval поставляется с возможностью явной передачи списка функций или переменных, к которым она может получить доступ. Нам нужно передать его в качестве аргумента в виде словаря.
Рассмотрим пример ниже:
|
Теперь, если мы попытаемся запустить вышеуказанную программу, как:
Enter the function(in terms of x):secret_function()
Enter the value of x:0
Мы получаем вывод:
NameError: name 'secret_function' is not defined
Давайте проанализируем приведенный выше код шаг за шагом:
- Прежде всего, мы создаем список методов, которые мы хотим разрешить как safe_list .
- Далее мы создаем словарь безопасных методов. В этом словаре ключи — это имена методов, а значения — их локальные пространства имен.
safe_dict = dict([(k, locals().get(k, None)) for k in safe_list])
locals () — это встроенный метод, который возвращает словарь, который отображает все методы и переменные в локальной области с их пространствами имен.
safe_dict['x'] = x
Здесь мы добавляем локальную переменную x в safe_dict. Никакая локальная переменная, кроме x, не будет идентифицирована функцией eval .
- eval принимает словари локальных и глобальных переменных в качестве аргументов. Итак, чтобы гарантировать, что ни один из встроенных методов не будет доступен для выражения eval , мы передаем другой словарь вместе с safe_dict , как показано ниже:
y = eval(expr, {"__builtins__":None}, safe_dict)
Таким образом, мы сделали нашу функцию eval безопасной от любых возможных взломов!
Использование Eval
eval не так часто используется по соображениям безопасности, как мы исследовали выше.
Тем не менее, это удобно в некоторых ситуациях, таких как:
- Возможно, вы захотите использовать его, чтобы позволить пользователям вводить свои собственные «скриптлеты»: маленькие выражения (или даже небольшие функции), которые можно использовать для настройки поведения сложной системы.
- eval также иногда используется в приложениях, нуждающихся в оценке математических выражений. Это намного проще, чем писать анализатор выражений.
Этот блог предоставлен Нихилом Кумаром . Если вам нравится GeeksforGeeks и вы хотели бы внести свой вклад, вы также можете написать статью, используя contrib.geeksforgeeks.org, или отправить свою статью на электронный адрес [email protected]. Смотрите свою статью, появляющуюся на главной странице GeeksforGeeks, и помогите другим вундеркиндам.
Пожалуйста, пишите комментарии, если вы обнаружите что-то неправильное или вы хотите поделиться дополнительной информацией по обсуждаемой выше теме.
Рекомендуемые посты:
Eval в Python
0.00 (0%) 0 votes
И снова про опасность eval() / Хабр
Сколько было сломано копий при обсуждении вопроса «Возможно ли сделать eval
безопасным?» — невозможно сосчитать. Всегда находится кто-то, кто утверждает, что нашёл способ оградиться от всех возможных последствий выполнения этой функции.
Когда мне понадобилось найти развёрнутый ответ на этот вопрос, я наткнулся на один пост. Меня приятно удивила глубина исследования, так что я решил, что это стоит перевести.
Коротко о проблеме
В Python есть встроенная функция eval()
, которая выполняет строку с кодом и возвращает результат выполнения:
assert eval("2 + 3 * len('hello')") == 17
Это очень мощная, но в то же время и очень опасная инструкция, особенно если строки, которые вы передаёте в eval
, получены не из доверенного источника. Что будет, если строкой, которую мы решим скормить eval
‘у, окажется os.system('rm -rf /')
? Интерпретатор честно запустит процесс удаления всех данных с компьютера, и хорошо ещё, если он будет выполняться от имени наименее привилегированного пользователя (в последующих примерах я буду использовать clear
(cls
, если вы используете Windows) вместо rm -rf /
, чтобы никто из читателей случайно не выстрелил себе в ногу).
Какие есть решения?
Некоторые утверждают, что возможно сделать eval
безопасным, если запускать его без доступа к символам из globals. В качестве второго (опционального) аргумента eval()
принимает словарь, который будет использован вместо глобального пространства имён (все классы, методы, переменные и пр., объявленные на «верхнем» уровне, доступные из любой точки кода) кодом, который будет выполнен eval
‘ом. Если eval
вызывается без этого аргумента, он использует текущее глобальное пространство имён, в которое мог быть импортирован модуль os
. Если же передать пустой словарь, глобальное пространство имён для eval
‘а будет пустым. Вот такой код уже не сможет выполниться и возбудит исключение NameError: name 'os' is not defined
:
eval("os.system('clear')", {})
Однако мы всё ещё можем импортировать модули и обращаться к ним, используя встроенную функцию __import__
. Так, код ниже отработает без ошибок:
eval("__import__('os').system('clear')", {})
Следующей попыткой обычно становится решение запретить доступ к __builtins__
изнутри eval
‘a, так как имена, подобные __import__, доступны нам потому, что они находятся в глобальной переменной __builtins__
. Если мы явно передадим вместо неё пустой словарь, код ниже уже не сможет быть выполнен:
eval("__import__('os').system('clear')", {'__builtins__':{}}) # NameError: name '__import__' is not defined
Ну а теперь-то мы в безопасности?
Некоторые говорят, что «да» и совершают ошибку. Для примера, вот этот небольшой кусок кода вызовет segfault
, если вы запустите его в CPython:
s = """
(lambda fc=(
lambda n: [
c for c in
().__class__.__bases__[0].__subclasses__()
if c.__name__ == n
][0]
):
fc("function")(
fc("code")(
0,0,0,0,"KABOOM",(),(),(),"","",0,""
),{}
)()
)()
"""
eval(s, {'__builtins__':{}})
Итак, давайте разберёмся, что же здесь происходит. Начнём с этого:
().__class__.__bases__[0]
Как многие могли догадаться, это просто один из способов обратиться к object
. Мы не можем просто написать object
, так как __builtins__
пусты, но мы можем создать пустой кортеж (тьюпл), первым базовым классом которого является object
и, пройдясь по его свойствам, получить доступ к классу object
.
Теперь мы получаем список всех классов, которые наследуют object
или, иными словами, список всех классов, объявленных в программе на данный момент:
().__class__.__bases__[0].__subclasses__()
Если заменить для удобочитаемости это выражение на ALL_CLASSES
, нетрудно будет заметить, что выражение ниже находит класс по его имени:
[c for c in ALL_CLASSES if c.__name__ == n][0]
Далее в коде нам надо будет дважды искать класс, так что создадим функцию:
lambda n: [c for c in ALL_CLASSES if c.__name__ == n][0]
Чтобы вызвать функцию, надо как-то её назвать, но, так как мы будем выполнять этот код внутри eval
‘a, мы не можем ни объявить функцию (используя def
), ни использовать оператор присвоения, чтобы привязать нашу лямбду к какой-нибудь переменной.
Однако, есть и третий вариант: параметры по умолчанию. При объявлении лямбды, как и при объявлении любой обычной функции, мы можем задать параметры по умолчанию, так что если мы поместим весь код внутри ещё одной лямбды, и зададим ей нашу, как параметр по умолчанию, — мы добьёмся желаемого:
(lambda fc=(
lambda n: [
c for c in ALL_CLASSES if c.__name__ == n
][0]
):
# теперь мы можем обращаться к нашей лямбде через fc
)()
Итак, мы имеем функцию, которая умеет искать классы, и можем обращаться к ней по имени. Что дальше? Мы создадим объект класса code
(внутренний класс, его экземпляром, например, является свойство func_code
объекта функции):
fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,"")
Из всех инициализующих параметров нас интересует только «KABOOM». Это и есть последовательность байт-кодов, которую будет использовать наш объект, и, как вы уже могли догадаться, эта последовательность не является «хорошей». На самом деле любого байт-кода из неё хватило бы, так как всё это — бинарные операторы, которые будут вызваны при пустом стеке, что приведёт к segfault
‘у CPython. «KABOOM» просто выглядит забавнее, спасибо lvh за этот пример.
Итак, у нас есть объект класса code
, но напрямую выполнить его мы не можем. Тогда создадим функцию, кодом которой и будет наш объект:
fc("function")(CODE_OBJECT, {})
Ну и теперь, когда у нас есть функция, мы можем её выполнить. Конкретно эта функция попытается выполнить наш некорректно составленный байт-код и приведёт к краху интерпретатора.
Вот весь код ещё раз:
(lambda fc=(lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0]):
fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})()
)()
Заключение
Итак, надеюсь теперь ни у кого не осталось сомнений в том, что eval
НЕ БЕЗОПАСЕН, даже если убрать доступ к глобальным и встроенным переменным.
В примере выше мы использовали список всех подклассов класса object
, чтобы создать объекты классов code
и function
. Точно таким же образом можно получить (и инстанцировать) любой класс, существующий в программе на момент вызова eval()
.
Вот ещё один пример того, что можно сделать:
s = """
[
c for c in
().__class__.__bases__[0].__subclasses__()
if c.__name__ == "Quitter"
][0](0)()
"""
eval(s, {'__builtins__':{}})
Модуль lib/site.py содержит класс Quitter
, который вызывается интерпретатором, когда вы набираете quit()
.
Код выше находит этот класс, инстанциирует его и вызывает, чем завершает работу интерпретатора.
Сейчас мы запускали eval
в пустом окружении, исходя из того, что указанный в статье код — это весь код нашей программы.
В случае использования eval
‘а в реальном приложении злоумышленник может получить доступ ко всем классам, которые вы используете, так что его возможности не будут ограничены практически ничем.
Проблема всех подобных попыток сделать eval
безопасным в том, что они все основаны на идее «чёрных списков», идее о том, что надо убрать доступ ко всем вещам, которые, как нам кажется, могут быть опасны при использовании в eval
‘е. С такой стратегией практически нет шансов на победу, ведь если окажется незапрещённым хоть что-то, система будет уязвима.
Когда я проводил исследование этой темы, я наткнулся на защищенный режим выполнения eval
‘а в Python, который является ещё одной попыткой побороть эту проблему:
>>> eval("(lambda:0).func_code", {'__builtins__':{}})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
RuntimeError: function attributes not accessible in restricted mode
Вкратце, он работает следующим образом: если __builtins__
внутри eval
отличаются от «официальных» — eval
переходит в защищенный режим, в котором закрыт доступ к некоторым опасным свойствам, таким как func_code
у функций. Более подробное описание этого режима можно найти тут, но, как мы уже видели выше, он тоже не является «серебряной пулей».
И всё-таки, можно ли сделать eval
безопасным? Сложно сказать. Как мне кажется, злоумышленнику не удастся навредить без доступа к объектам с двумя нижними подчёркиваниями, обрамляющими имя, так что возможно, если исключить из обработки все строки с двумя нижними подчёркиваниями, то мы будем в безопасности. Возможно…
P.S.
В треде на Reddit я нашёл короткий сниппет, позволяющий нам в eval получить «оригинальные» __builtins__:
[
c for c in ().__class__.__base__.__subclasses__()
if c.__name__ == 'catch_warnings'
][0]()._module.__builtins__
Традиционное P.P.S. для хабра: прошу обо всех ошибках, неточностях и опечатках писать в личку 🙂
Python eval ()
Проще говоря, функция eval ()
запускает код Python (который передается в качестве аргумента) в программе.
Синтаксис eval ()
:
eval (выражение, globals = None, locals = None)
Параметры eval ()
Функция eval ()
принимает три параметра:
- выражение — строка анализируется и оценивается как выражение Python
- globals (необязательно) — словарь
- locals (необязательно) — объект отображения.Словарь — это стандартный и часто используемый тип сопоставления в Python.
Использование глобальных переменных и локальных будет обсуждаться позже в этой статье.
Возвращаемое значение из eval ()
Метод eval () возвращает результат, вычисленный на основе выражения .
Пример 1: Как eval () работает в Python
х = 1
print (eval ('x + 1'))
Выход
2
Здесь функция eval ()
вычисляет выражение x + 1
, а print
используется для отображения этого значения.
Пример 2: Практический пример для демонстрации использования eval ()
# Периметр площади
def вычислитьПериметр (l):
возврат 4 * л
# Площадь квадрата
def вычислитьArea (l):
вернуть л * л
выражение = input ("Введите функцию:")
для l в диапазоне (1, 5):
if (выражение == 'calculatePerimeter (l)'):
print ("Если длина", l, ", Perimeter =", eval (выражение))
elif (выражение == 'calculateArea (l)'):
print ("Если длина", l, ", Area =", eval (выражение))
еще:
print ('Неправильная функция')
перерыв
Выход
Введите функцию: calculateArea (l) Если длина 1, Площадь = 1 Если длина 2, площадь = 4 Если длина 3, площадь = 9 Если длина 4, Площадь = 16
Предупреждения при использовании eval ()
Рассмотрим ситуацию, когда вы используете систему Unix (macOS, Linux и т. Д.) И импортировали модуль os
.Модуль os предоставляет переносимый способ использования функций операционной системы, таких как чтение или запись в файл.
Если вы разрешаете пользователям вводить значение с помощью eval (input ())
, пользователь может вводить команды для изменения файла или даже удаления всех файлов с помощью команды: os.system ('rm -rf *')
.
Если вы используете в своем коде eval (input ())
, рекомендуется проверить, какие переменные и методы может использовать пользователь. Вы можете увидеть, какие переменные и методы доступны, используя метод dir ().
из математического импорта *
print (eval ('dir ()'))
Выход
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'comb', 'copysign', 'cos', 'ch', 'градусы', 'dist', 'e', 'erf ',' erfc ',' exp ',' expm1 ',' fabs ',' факториал ',' floor ',' fmod ',' frexp ',' fsum ',' gamma ',' gcd ',' hypot ', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'isqrt', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf ',' nan ',' os ',' perm ',' pi ',' pow ',' prod ',' радианы ',' остаток ',' sin ',' sinh ',' sqrt ',' tan ', «tanh», «tau», «trunc»]
Ограничение использования доступных методов и переменных в eval ()
Чаще всего все доступные методы и переменные, используемые в выражении (первый параметр eval ()
), могут не понадобиться или даже могут иметь брешь в безопасности.Возможно, вам потребуется ограничить использование этих методов и переменных для eval ()
. Вы можете сделать это, передав необязательные параметры globals и locals (словари) в функцию eval ()
.
1. Если пропущены и глобальные, и локальные параметры
Если оба параметра опущены (как в наших предыдущих примерах), выражение выполняется в текущей области. Вы можете проверить доступные переменные и методы, используя следующий код:
print (eval ('dir ()')
2.Передача глобальных параметров; параметр locals опущен
Глобальные переменные и локальные параметры (словари) используются для глобальных и локальных переменных соответственно. Если словарь locals опущен, по умолчанию используется словарь globals . Это означает, что глобальные переменные будут использоваться как для глобальных, так и для локальных переменных.
Примечание: Вы можете проверить текущий глобальный и локальный словарь в Python, используя встроенные методы globals () и locals () соответственно.
Пример 3: Передача пустого словаря в качестве глобального параметра
из математического импорта *
print (eval ('dir ()', {}))
# Код вызовет исключение
print (eval ('sqrt (25)', {}))
Выход
['__builtins__'] Отслеживание (последний вызов последний): Файл «<строка>», строка 5, в <модуле> print (eval ('sqrt (25)', {})) Файл «<строка>», строка 1, в <модуле> NameError: имя sqrt не определено
Если вы передадите пустой словарь как globals , только __builtins__
будут доступны для выражения
(первый параметр для eval ()
).
Несмотря на то, что мы импортировали модуль math
в приведенную выше программу, выражение выражение не может получить доступ к функциям, предоставляемым модулем math.
Пример 4: Доступность некоторых методов
из математического импорта *
print (eval ('dir ()', {'sqrt': sqrt, 'pow': pow}))
Выход
['__builtins__', 'pow', 'sqrt']
Здесь выражение может использовать только методы sqrt ()
и pow ()
вместе с __builtins__
.
Также возможно изменить имя метода, доступного для выражения по вашему желанию:
из математического импорта *
name = {'квадратный_корень': sqrt, 'power': pow}
print (eval ('директория ()', имена))
# Использование square_root в Expression
print (eval ('квадратный_корень (9)', имена))
Выход
['__builtins__', 'power', 'square_root'] 3,0
В приведенной выше программе square_root ()
вычисляет квадратный корень, используя sqrt ()
.Однако попытка использовать sqrt ()
напрямую вызовет ошибку.
Пример 5: Ограничение использования встроенных модулей
Вы можете ограничить использование __builtins__
в выражении следующим образом:
eval (выражение, {'__builtins__': None})
3. Передача и глобальных, и локальных словаря
Вы можете сделать необходимые функции и переменные доступными для использования, передав словарь locals .Например:
из математического импорта *
а = 169
print (eval ('sqrt (a)', {'__builtins__': None}, {'a': a, 'sqrt': sqrt}))
Выход
13,0
В этой программе выражение может иметь sqrt ()
метод и переменную только . Все остальные методы и переменные недоступны.
Ограничение использования eval ()
путем передачи глобальных и локальных словарей сделает ваш код безопасным, особенно когда вы используете ввод, предоставленный пользователем для метода eval ()
.
Примечание: Иногда eval ()
небезопасен даже с ограниченными именами. Когда объект и его методы становятся доступными, можно делать почти все. Единственный безопасный способ — это проверить введенные пользователем данные.
.Встроенные функции
— документация Python 3.8.6
Интерпретатор Python имеет ряд встроенных функций и типов, которые
всегда доступны. Они перечислены здесь в алфавитном порядке.
Встроенные функции | ||||
---|---|---|---|---|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
.
Функция Python eval () — JournalDev
Функция Python eval () используется для синтаксического анализа строки выражения как выражения Python и последующего ее выполнения.
Функция eval () Python
Сигнатура функции eval () Python:
eval (выражение, globals = None, locals = None)
выражение — обязательный строковый параметр, он анализируется и выполняется как выражение Python.
globals — словарь, используемый для определения выражений, доступных для выполнения.Стандартные встроенные методы доступны, если явно не ограничены с помощью элемента '__builtins__': None
.
locals — используется для указания локальных переменных и методов, доступных для функции eval ().
Пример функции eval () в Python
Давайте сначала рассмотрим простой пример функции eval () в Python.
х = 1
печать (eval ('x == 1'))
печать (eval ('x + 2'))
Выход:
Правда
3
Python eval () с пользовательским вводом
Приведенный выше пример функции eval () очень ограничен и не оправдывает ее возможности.Сила функции eval () заключается в динамическом выполнении операторов. Мы можем выполнять произвольные объекты кода с помощью функции eval ().
Давайте посмотрим на более сложный пример, где мы попросим пользователя ввести функции для выполнения.
# eval () с пользовательским вводом
из математического импорта *
для l в диапазоне (1, 3):
func = input ("Введите математическую функцию для вычисления: \ n")
пытаться:
печать (eval (функция))
кроме исключения как ex:
печать (например)
сломать
print ('Готово')
На изображении ниже показан пример выполнения указанного выше скрипта Python.
Без функции eval мы не можем выполнять команды, введенные пользователем. В этом сила функции eval ().
Риски безопасности с функцией eval ()
С большой мощью приходит большая ответственность очень верно, если вы позволяете вводить данные пользователем в виде команды.
Что делать, если мы импортировали модуль ОС и пользователь вводит команду os.system ('rm -rf /')
для выполнения. Это приведет к удалению системных файлов и повреждению нашей среды.
Вот почему, когда вы используете функцию eval () для выполнения кода ввода пользователем, вам необходимо убедиться, что введенные пользователем данные сначала проверяются, и если они в порядке, то только выполняются. В этом случае пригодятся параметры globals и locals.
Python eval () глобальные и локальные объекты
Прежде чем мы решим, какие функции мы должны сделать доступными для eval (), нам нужно выяснить, какие все функции и переменные присутствуют в глобальной и локальной области. Мы можем найти эту информацию с помощью встроенных функций globals ()
, locals ()
и dir ()
.
Давайте посмотрим на пример, в котором мы узнаем, какие функции и переменные доступны в глобальной и локальной области.
из математического импорта *
def квадратный_корень (n):
вернуть sqrt (n)
print (globals ()) # словарь, представляющий текущую глобальную таблицу символов.
print (locals ()) # словарь, представляющий текущую локальную таблицу символов.
print (dir ()) # список имен в текущей локальной области
Выход:
{'__name__': '__main__', '__doc__': Нет, '__package__': Нет, '__loader__': <_frozen_importlib_external.Объект SourceFileLoader в 0x105b11400>, '__spec__': None, '__annotations__': {}, '__builtins__': , '__file__': '/ Users / pankaj / Documents / PycharmProjects / BasicPython /basic_examples/eval_example.py ',' __cached__ ': нет,' acos ': <встроенная функция acos>,' acosh ': <встроенная функция acosh>,' asin ': <встроенная функция asin>, 'asinh': <встроенная функция asinh>, 'atan': <встроенная функция atan>, 'atan2': <встроенная функция atan2>, 'atanh': <встроенная функция atanh>, 'ceil ': <встроенная функция ceil>,' copysign ': <встроенная функция copysign>,' cos ': <встроенная функция cos>,' cosh ': <встроенная функция cosh>,' градусы ': <степени встроенной функции>, 'erf': <встроенная функция erf>, 'erfc': <встроенная функция erfc>, 'exp': <встроенная функция exp>, 'expm1': <встроенная -in function expm1>, 'fabs': <встроенная функция fabs>, 'factorial': <встроенная функция factorial>, 'floor': <встроенная функция floor>, 'fmod': <встроенная function fmod>, 'frexp': <встроенная функция frexp>, 'fsum' : <встроенная функция fsum>, 'gamma': <встроенная функция gamma>, 'gcd': <встроенная функция gcd>, 'hypot': <встроенная функция hypot>, 'isclose': < встроенная функция isclose>, 'isfinite': <встроенная функция isfinite>, 'isinf': <встроенная функция isinf>, 'isnan': <встроенная функция isnan>, 'ldexp': <встроенная- в функции ldexp>, 'lgamma': <встроенная функция lgamma>, 'log': <встроенный журнал функций>, 'log1p': <встроенная функция log1p>, 'log10': <встроенная функция log10>, 'log2': <встроенная функция log2>, 'modf': <встроенная функция modf>, 'pow': <встроенная функция pow>, 'radians': <встроенная функция радиан> , 'остаток': <встроенная функция остаток>, 'sin': <встроенная функция sin>, 'sinh': <встроенная функция sinh>, 'sqrt': <встроенная функция sqrt>, ' tan ': <встроенная функция tan>,' tanh ': <встроенная функция tanh>,' trunc ': <встроенная функция trunc>,' pi ': 3.141592653589793, 'e': 2.718281828459045, 'tau': 6.283185307179586, 'inf': inf, 'nan': nan, 'square_root': }
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader объект в 0x105b11400>, '__spec__': None, '__innotations__': { : , '__file__': '/Users/pankaj/Documents/PycharmProjects/BasicPython/basic_examples/eval_example.py', '__cached__': нет, 'acos': <встроенный function acos>, 'acosh': <встроенная функция acosh>, 'asin': <встроенная функция asin>, 'asinh': <встроенная функция asinh>, 'atan': <встроенная функция atan >, 'atan2': <встроенная функция atan2>, 'atanh': <встроенная функция atanh>, 'ceil': <встроенная функция ceil>, 'copysign': <встроенная функция copysign>, 'cos': <встроенная функция cos>, 'cosh': <встроенная функция cosh>, 'градусы': <встроенные функции градусов>, 'erf': <встроенная функция erf>, 'erfc ': <встроенная функция erfc>,' exp ': <встроенная функция exp>,' expm1 ': <встроенная функция expm1>,' fabs ': <встроенная функция fabs>,' factorial ': <встроенная функция factorial>, 'floor' : <встроенная функция floor>, 'fmod': <встроенная функция fmod>, 'frexp': <встроенная функция frexp>, 'fsum': <встроенная функция fsum>, 'gamma': < встроенная функция gamma>, 'gcd': <встроенная функция gcd>, 'hypot': <гипотеза встроенной функции>, 'isclose': <встроенная функция isclose>, 'isfinite': <встроенная- in function isfinite>, 'isinf': <встроенная функция isinf>, 'isnan': <встроенная функция isnan>, 'ldexp': <встроенная функция ldexp>, 'lgamma': <встроенная функция lgamma>, 'log': <встроенный журнал функций>, 'log1p': <встроенная функция log1p>, 'log10': <встроенная функция log10>, 'log2': <встроенная функция log2> , 'modf': <встроенная функция modf>, 'pow': <встроенная функция pow>, 'radians': <встроенная функция радиан>, 'остаток': <встроенная функция остаток>, ' sin ': <встроенная функция sin>,' sinh ': <встроенная функция sinh>,' sqrt ': <встроенная функция sqrt>,' tan ': <встроенная функция tan>,' tanh ' : <встроенная функция tanh>, 'trunc': <встроенная функция trunc>, 'pi': 3.141592653589793, 'e': 2.718281828459045, 'tau': 6.283185307179586, 'inf': inf, 'nan': nan, 'square_root': }
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', ' asinh ',' atan ',' atan2 ',' atanh ',' ceil ',' copysign ',' cos ',' cosh ',' градусы ',' e ',' erf ',' erfc ',' exp ' , 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', ' isfinite ',' isinf ',' isnan ',' ldexp ',' lgamma ',' log ',' log10 ',' log1p ',' log2 ',' modf ',' nan ',' pi ',' pow ' , 'радианы', 'остаток', 'sin', 'sinh', 'sqrt', 'square_root', 'tan', 'tanh', 'tau', 'trunc']
Это множество функций, к которым у eval () будет доступ.Большинство из них взяты из модуля __builtins__ и math.
Давайте посмотрим, что произойдет, если мы укажем значение globals
как пустой словарь в функции eval.
print (eval ('dir ()', {}))
Выход:
['__builtins__']
Итак, встроенные методы все еще доступны для функции eval. Если вы хотите ограничить доступ только к нескольким встроенным методам, вы можете указать его значение для глобальных переменных. Например, приведенный ниже код позволяет функции eval () выполнять только встроенную функцию min.
print (eval ('min (1,2)', {'__ builtins __': {'min': min}})) # 1
Давайте посмотрим на другой пример, в котором я предоставляю значение locals и отключаю доступ ко всем встроенным функциям для eval ().
у = 5
print (eval ('y + 1', {'__ builtins__': None}, {'y': y})) # 6
Давайте посмотрим на последний пример, в котором я разрешаю доступ только к некоторым методам из математического модуля. Мы также сопоставляем square_root с sqrt для лучшей читаемости человеком.
из математического импорта *
для l в диапазоне (1, 3):
func = input ("Введите математическую функцию для вычисления. \ nРазрешенные функции: square_root (x) и pow (x, y): \ n")
пытаться:
print (eval (func, {'квадратный_корень': sqrt, 'pow': pow}))
кроме исключения как ex:
печать (например)
сломать
print ('Готово')
Пример вывода:
Введите математическую функцию для оценки.
Допустимые функции: square_root (x) и pow (x, y):
квадрат_корень (16)
4.0
Введите математическую функцию для оценки.Допустимые функции: square_root (x) и pow (x, y):
log10 (100)
имя 'log10' не определено
Готово
Заметили ли вы какой-либо изъян в приведенном выше коде?
Я ничего не указал для встроенных функций, поэтому они будут доступны для функции eval ().
Ниже приведен еще один пример выполнения, показывающий, что встроенные функции доступны для выполнения.
Введите математическую функцию для оценки.
Допустимые функции: square_root (x) и pow (x, y):
мин (5,4)
4
Введите математическую функцию для оценки.Допустимые функции: square_root (x) и pow (x, y):
макс (10,20)
20
Резюме
Функция eval () в Python очень мощная. Несмотря на то, что у нас есть глобальные и локальные переменные для ограничения доступа, их недостаточно, и доступны обходные пути, чтобы нанести вред вашей системе. Прочтите эту статью, в которой объясняется, чем опасен eval. Вы не должны использовать функцию eval () с ненадежным пользовательским вводом.
Вы можете получить полный скрипт Python и другие примеры Python в нашем репозитории GitHub. .
Python функция eval () — ThePythonGuru.com
- Дом
- Встроенные функции Python
- Функция eval () Python
(Спонсоры) Начните изучать Python с помощью DataCamp’s
бесплатный вводный курс по Python.
Изучите науку о данных, выполняя интерактивные задания по программированию и просматривая видео опытных инструкторов.Начать сейчас!
Обновлено 7 января 2020 г.
eval ()
позволяет нам выполнять произвольные строки как код Python. Он принимает исходную строку и возвращает объект.
Его синтаксис следующий:
Синтаксис:
eval (expr, globals = None, locals = None)
ПАРАМЕТР | ОПИСАНИЕ |
---|---|
expr (обязательно) | expr может быть любым допустимым выражением Python |
глобальные (опционально) | Глобальное пространство имен для использования при выполнении исходного кода.Это должен быть словарь. Если не указан, будет использоваться текущее глобальное пространство имен. |
местные (опционально) | Локальное пространство имен для использования при выполнении исходного кода. Это может быть любое отображение. Если опущено, по умолчанию используется словарь globals . |
Если опущены и глобальные
, и локальные
, используются текущие глобальное и локальное пространства имен.
Вот пример, демонстрирующий, как работает eval ()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 год 22 23 24 | >>> >>> eval ("5 == 5") Правда >>> >>> >>> eval ("4 <10") Правда >>> >>> >>> eval ("8 + 4 - 2 * 3") 6 >>> >>> >>> eval ("'py' * 5") 'py py py py py' >>> >>> >>> eval ("10 ** 2") 100 >>> >>> >>> eval ("'привет' + 'ру'") 'привет' >>> |
Попробовать:
print (eval ("5 == 5"))
print (eval ("4 <10"))
print (eval ("8 + 4 - 2 * 3"))
print (eval ("'py' * 5"))
print (eval ("10 ** 2"))
print (eval ("'привет' + 'py'"))
eval ()
не ограничивается простым выражением.Мы можем выполнять функции, вызывать методы, ссылочные переменные и так далее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | >>> >>> eval ("абс (-11)") 11 >>> >>> >>> eval ('"привет" .upper ()') 'ЗДРАВСТВУЙТЕ' >>> >>> >>> import os >>> >>> >>> eval ('os.getcwd ()') # получить текущий рабочий каталог '/ главная / thepythonguru' >>> >>> >>> х = 2 >>> >>> eval ("x + 4") # x упоминается внутри выражения 6 >>> |
Попробовать:
print (eval ("abs (-11)"))
print (eval ('"привет".верхний () '))
импорт ОС
# получить текущий рабочий каталог
печать (eval ('os.getcwd ()'))
х = 2
print (eval ("x + 4")) # x упоминается внутри выражения
Обратите внимание, что eval ()
работает только с выражением. Попытка передать оператор вызывает SyntaxError
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | >>> >>> eval ('a = 1') # оператор присваивания Отслеживание (последний вызов последний): Файл " |
Evil eval ()
Никогда не следует передавать ненадежный источник в функцию eval ()
напрямую.Поскольку злоумышленнику довольно легко нанести ущерб вашей системе. Например, следующий код можно использовать для удаления всех файлов из системы.
>>> eval ('os.system ("RM -RF /")') # команда намеренно написана с заглавной буквы >>> |
Приведенный выше код завершится ошибкой, если модуль os недоступен в вашей текущей глобальной области. Но мы можем легко обойти это, используя встроенную функцию __import __ ()
.
>>> >>> eval ("__ import __ ('os'). system ('RM -RF /')") # команда намеренно написана с заглавной буквы >>> |
Итак, есть ли способ сделать eval ()
безопасным?
Определение пространств имен
eval ()
дополнительно принимает два сопоставления, которые служат глобальным и локальным пространствами имен для выражения, которое должно быть выполнено. Если отображение (я) не предоставлено, будут использоваться текущие значения глобального и локального пространств имен.
Вот несколько примеров:
Пример 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> >>> globals = { ... 'a': 10, ... "фрукты": ["манго", "персики", "бананы"], ...} >>> >>> >>> locals = {} >>> >>> >>> eval ("str (a) + '' + fruit [0]", globals, locals) '10 манго ' >>> |
Пример 2:
>>> >>> eval ('абс (-100)', {}, {}) 100 >>> |
Несмотря на то, что мы передали пустые словари как глобальные и локальные пространства имен, eval ()
по-прежнему имеет доступ к встроенным модулям (т.е.e __builtins__
).
>>> >>> директория (__ builtins__) ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', ... ... 'property', 'quit', 'range', 'repr', 'reverse', 'round', 'set', 'setattr', 'slice', 'отсортировано' , 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'] >>> |
Чтобы удалить встроенные модули из глобального пространства имен, передайте словарь, содержащий ключ __builtins__
со значением None
.
Пример 3:
>>> >>> eval ('abs (-100)', {'__builtins __': None}, {}) Отслеживание (последний вызов последний): Файл " |
Даже после удаления доступа к встроенным функциям, eval ()
все еще небезопасен. Рассмотрим следующий список.
>>> >>> eval ("5 ** 98765432111123", {'__builtins __': None}, {}) >>> |
Этого обманчиво простого на вид выражения достаточно, чтобы заглушить ваш процессор.
Ключевой вывод - используйте eval ()
только с доверенным источником.
Другие учебные пособия (спонсоры)
Этот сайт щедро поддерживается
DataCamp. DataCamp предлагает интерактивные онлайн
Учебники по Python
для науки о данных.Присоединяйтесь к более чем миллиону других учеников и получите
начал изучать Python для науки о данных сегодня!
Пожалуйста, включите JavaScript, чтобы просматривать комментарии от Disqus.
.