Используя 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'))

работает как шарм!

Другие вопросы по тегам