Используя python eval() против ast.literal_eval()?
У меня есть ситуация с некоторым кодом, где eval()
придумал как возможное решение. Теперь мне никогда не приходилось пользоваться eval()
прежде, но я столкнулся с большим количеством информации о потенциальной опасности, которую это может вызвать. Тем не менее, я очень осторожен с его использованием.
Моя ситуация такова, что у меня есть ввод данных от пользователя:
datamap = raw_input('Provide some data here: ')
куда datamap
должен быть словарь. Я искал вокруг и обнаружил, что eval()
мог бы решить это. Я подумал, что смогу проверить тип ввода, прежде чем пытаться использовать данные, и это будет реальной мерой безопасности.
datamap = eval(raw_input('Provide some data here: ')
if not isinstance(datamap, dict):
return
Я прочитал документы, и мне все еще неясно, будет ли это безопасно или нет. Оценивает ли eval данные сразу после их ввода или после datamap
переменная называется?
Это ast
модуля .literal_eval()
единственный безопасный вариант?
8 ответов
datamap = eval(raw_input('Provide some data here: '))
означает, что вы действительно оцениваете код, прежде чем сочтете его небезопасным или нет. Он оценивает код, как только вызывается функция. Смотрите также опасностиeval
,
ast.literal_eval
вызывает исключение, если ввод не является допустимым типом данных Python, поэтому код не будет выполнен, если это не так.
использование ast.literal_eval
всякий раз, когда вам нужно eval
, Обычно вы не должны оценивать буквальные выражения Python.
ast.literal_eval()
допустимо только небольшое подмножество синтаксиса Python:
Предоставленная строка или узел могут состоять только из следующих литеральных структур Python: строк, чисел, кортежей, списков, диктов, логических значений и None.
Переходя __import__('os').system('rm -rf /a-path-you-really-care-about')
в ast.literal_eval()
выдаст ошибку, но eval()
с удовольствием протерет ваш диск.
Поскольку, похоже, вы позволяете пользователю вводить простой словарь, используйте ast.literal_eval()
, Он безопасно делает то, что вы хотите, и ничего больше.
eval: это очень мощно, но также очень опасно, если вы принимаете строки для оценки из ненадежного ввода. Предположим, что оцениваемой строкой является "os.system('rm -rf /')"? Это действительно начнет удалять все файлы на вашем компьютере.
ast.literal_eval: безопасно оценивать узел выражения или строку, содержащую литерал Python или отображение контейнера. Предоставленная строка или узел могут состоять только из следующих литеральных структур Python: строк, байтов, чисел, кортежей, списков, диктов, наборов, логических значений, None, байтов и наборов.
Синтаксис:
eval(expression, globals=None, locals=None)
import ast
ast.literal_eval(node_or_string)
Пример:
# python 2.x - doesn't accepts operators in the string
import ast
ast.literal_eval('[1, 2, 3]') # output: [1, 2, 3]
ast.literal_eval('1+1') # output: ValueError: malformed string
# python 3.0 -3.6
import ast
ast.literal_eval("1+1") # output : 2
ast.literal_eval("{'a': 2, 'b': 3, 3:'xyz'}") # output : {'a': 2, 'b': 3, 3:'xyz'}
# type dictionary
ast.literal_eval("",{}) # output : Syntax Error required only one parameter
ast.literal_eval("__import__('os').system('rm -rf /')") # output : error
eval("__import__('os').system('rm -rf /')")
# output : start deleting all the files on your computer.
# restricting using global and local variables
eval("__import__('os').system('rm -rf /')",{'__builtins__':{}},{})
# output : Error due to blocked imports by passing '__builtins__':{} in global
# But still eval is not safe. we can access and break the code as given below
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__':{}})
Начиная с python 3.7 ast.literal_eval() теперь стал более строгим. Сложение и вычитание произвольных чисел больше не допускаются. ссылка на сайт
Python стремится к своей оценке, поэтому eval(raw_input(...))
оценит ввод пользователя, как только он попадет eval
независимо от того, что вы делаете с данными впоследствии. Поэтому это небезопасно, особенно когда вы eval
пользовательский ввод.
использование ast.literal_eval
,
Например, ввод этого в приглашении будет очень и очень плохим для вас:
__import__('os').system('rm -rf /a-path-you-really-care-about')
В недавнем Python3 ast.literal_eval() больше не анализирует простые строки, вместо этого вы должны использовать метод ast.parse () для создания AST, а затем его интерпретации.
Это полный пример правильного использования ast.parse () в Python 3.6+ для безопасного вычисления простых арифметических выражений.
import ast, operator, math
import logging
logger = logging.getLogger(__file__)
def safe_eval(s):
def checkmath(x, *args):
if x not in [x for x in dir(math) if not "__" in x]:
raise SyntaxError(f"Unknown func {x}()")
fun = getattr(math, x)
return fun(*args)
binOps = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Mod: operator.mod,
ast.Pow: operator.pow,
ast.Call: checkmath,
ast.BinOp: ast.BinOp,
}
unOps = {
ast.USub: operator.neg,
ast.UAdd: operator.pos,
ast.UnaryOp: ast.UnaryOp,
}
ops = tuple(binOps) + tuple(unOps)
tree = ast.parse(s, mode='eval')
def _eval(node):
if isinstance(node, ast.Expression):
logger.debug("Expr")
return _eval(node.body)
elif isinstance(node, ast.Str):
logger.debug("Str")
return node.s
elif isinstance(node, ast.Num):
logger.debug("Num")
return node.value
elif isinstance(node, ast.Constant):
logger.info("Const")
return node.value
elif isinstance(node, ast.BinOp):
logger.debug("BinOp")
if isinstance(node.left, ops):
left = _eval(node.left)
else:
left = node.left.value
if isinstance(node.right, ops):
right = _eval(node.right)
else:
right = node.right.value
return binOps[type(node.op)](left, right)
elif isinstance(node, ast.UnaryOp):
logger.debug("UpOp")
if isinstance(node.operand, ops):
operand = _eval(node.operand)
else:
operand = node.operand.value
return unOps[type(node.op)](operand)
elif isinstance(node, ast.Call):
args = [_eval(x) for x in node.args]
r = checkmath(node.func.id, *args)
return r
else:
raise SyntaxError(f"Bad syntax, {type(node)}")
return _eval(tree)
if __name__ == "__main__":
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
logger.addHandler(ch)
assert safe_eval("1+1") == 2
assert safe_eval("1+-5") == -4
assert safe_eval("-1") == -1
assert safe_eval("-+1") == -1
assert safe_eval("(100*10)+6") == 1006
assert safe_eval("100*(10+6)") == 1600
assert safe_eval("2**4") == 2**4
assert safe_eval("sqrt(16)+1") == math.sqrt(16) + 1
assert safe_eval("1.2345 * 10") == 1.2345 * 10
print("Tests pass")
Если все, что вам нужно, это пользовательский словарь, возможно, лучшим решением будет json.loads
, Основным ограничением является то, что для json требуется строковые ключи. Также вы можете предоставить только буквальные данные, но это также относится и к literal_eval
,
Я застрял с ast.literal_eval()
, Я пробовал это в отладчике IntelliJ IDEA, и он продолжал возвращаться None
на выходе отладчика.
Но позже, когда я назначил ее вывод переменной и напечатал ее в коде. Работало нормально. Пример совместного использования кода:
import ast
sample_string = '[{"id":"XYZ_GTTC_TYR", "name":"Suction"}]'
output_value = ast.literal_eval(sample_string)
print(output_value)
Его версия Python 3.6.
вместо импорта
ast
, я просто приписал
eval(data.decode('utf-8))
в переменную словаря, например:
dict = eval(data.decode('utf-8'))
работает как шарм!