这个eval()函数是否存在安全漏洞?

3

我希望创建一个函数装饰器,在执行实际函数之前,可以对其变量执行某些操作。我想提供这些操作作为eval() 字符串。变量是函数的参数。让我举个例子:

from functools import wraps
from inspect import getcallargs


def safeornot(*keys):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # get dict of arguments passed to the function
            func_args = getcallargs(func, *args, **kwargs)
            # now these are made locally visible as normal variables inside the eval function
            _keys = []
            for key in keys:
                _key = eval(key, globals(), func_args)
                _keys.append(_key)
            print(_keys)
            return func(*args, **kwargs)
        return wrapper
    return decorator


@safeornot('you + " " + __name__', 'you + " " + me')
def you_and_me(you, me):
    print("you and me")

you_and_me("1", "2")

将会显然地打印出:

['1 __main__', '1 2']
you and me

我瞄准的目标是IT技术相关内容。

但是这个函数将用于一个不安全的环境:它将成为Web应用程序内部函数的速率限制装饰器,因此youme变量就像它们所代表的一样不安全。

eval()函数是否可以被攻击,例如格式化服务器?我不太能看到它被黑客攻击了,因为locals()没有被调用且被视为不可调用对象(在此例中为str)。

有任何黑客想法吗?

更新:

  • 任何人都可以调用装饰过的函数。
  • 装饰过的函数参数可以是任何内容。
  • *keys参数只能由代码作者填写。

它的预期用途如下:

@ratelimit('some_custom_id_func(["by-username", username])').at('5/15s')
def login(username, password):
    ...

更新[2]:

如果:

# get dict of arguments passed to the function
func_args = getcallargs(func, *args, **kwargs)
# THE CHANGE IS HERE!
for func_arg_key in func_args.keys():
    func_args[func_arg_key] = str(func_args[func_arg_key]) 
# NOW INPUT IS SANITIZED (KINDA)?
# now these are made locally visible as normal variables inside the eval function
_keys = []
for key in keys:
    _key = eval(key, globals(), func_args)
    _keys.append(_key)
print(_keys)

输入进行了净化处理吗?

更新 [3]

想象一下,我们摆脱了eval()。这会让它更安全吗?如果是这样,为什么?

def ratelimit(*keys):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # get dict of arguments passed to the function
            func_args = getcallargs(func, *args, **kwargs)
            for key in keys:
                print(key(func_args))
            return func(*args, **kwargs)
        return wrapper
    return decorator


@ratelimit(lambda d: d['you'] + " and " + d['me'], lambda d: d['me'] + " or " + d['you'])
def you_and_me(you, me):
    print("you and me")


you_and_me("1", "2")

3
在回答“是否安全”之前,我们需要知道谁能够访问什么?谁将能够使用@safeornot编写代码?谁将能够调用带有@safeornot修饰的代码?是谁在创建调用这些方法的参数? - Frank Yellin
你真的需要传入 globals() 吗?使 eval 更安全的一件事是不提供任何全局变量:https://www.programiz.com/python-programming/methods/built-in/eval - cadolphs
2
@Lagerbaer 在这里说,即使传入空字典,也可以访问globals()。虽然我没有尝试过,但没有理由不相信。不过我已经将其设置为可选项,并默认传入一个空字典。 - winwin
@winwin,我现在已经失去了你试图控制的目标。 - ekhumoro
1
@winwin 没有直接传递值的方法。您需要构建一个字符串,其中包含当前变量值进行评估。这就是我写“某种方式”的原因。也许可以使用 str.format()string.Template.substitute - VPfB
显示剩余9条评论
1个回答

2
我能给你的最好答案是“我不知道”。我想不到任何东西,但有很多比我聪明得多的人擅长破解。我讨厌eval,认为每次出现eval都是一个等待发生的安全漏洞。你应该尽可能避免使用它。
如果是我的话,我会写:
def login_logger(x, y):
   Do whatever you want here safely

@ratelimit(login_logger)
def login(x, y): ...

让你的@ratelimit调用一个日志函数,该函数与它所包装的函数具有完全相同的参数。


这里面的问题是,你需要为想要添加装饰器的每个函数编写这种类型的函数。这会在代码中看起来非常丑陋,而且更重要的是,你总是需要明确地自己编写参数。这真的很破坏装饰器本身的目的,因为它变成了首先组合它就成了一件痛苦的事情,而不是“只需加一些水”的简单操作。你知道我的意思吗? - winwin
是的,我同意。但是eval真的让我感到恐惧。即使这个人编写的代码是100%安全的,下一个程序员也可能会看到这个“漂亮”的工具,并编写不安全的代码。我曾经从事过代码安全方面的工作,我见过太多由于这种事情引起的错误。 - Frank Yellin
我已经摆脱了 eval 函数,请看更新。但是我真的没有看出它更安全了。不过,如果输入是任何东西,并且 eval 只用于传递变量,就像这里的字典一样,那有什么区别呢?eval 为什么更危险? - winwin
谷歌搜索“eval为什么是危险的?” 如果您能完全控制传递给eval所有内容,那么也许可以。 但您已经说涉及用户输入。 https://xkcd.com/327/。这是一个SQL示例,而不是Python示例,但同样的原则适用。 - Frank Yellin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接