确保不意外使用不安全的代码


10

函数f()eval()我创建并存储在local_file运行程序的计算机上的数据使用(或存在危险):

import local_file

def f(str_to_eval):
    # code....
    # .... 
    eval(str_to_eval)    
    # ....
    # ....    
    return None


a = f(local_file.some_str)

f() 安全运行,因为我提供的字符串是我自己的。

但是,如果我决定将其用于不安全的事物(例如用户输入),则可能会发生严重错误。另外,如果local_file停止在本地,则将创建漏洞,因为我需要信任也提供该文件的计算机。

如何确保我永远不会“忘记”该功能使用不安全(除非满足特定条件)?

注意:eval()很危险,通常可以用安全的东西代替。


1
我通常喜欢回答提出的问题,而不是说“您不应该这样做”,但是在这种情况下,我会例外。我非常确定您没有理由使用eval。这些功能包括在PHP和Python之类的语言中,作为一种概念验证,可以解释语言完成多少工作。从根本上讲,您无法编写可以创建代码的代码,但是不能将相同的逻辑编码到函数中。Python可能具有回调和函数绑定,可让您执行所需的操作
user1122069

1
我还需要信任提供该文件的机器 ”-您始终需要信任正在执行代码的机器。
Bergi

在文件中添加注释:“此字符串被评估;请不要在此处放置恶意代码”?
user253751'3

@immibis评论不足够。我当然会在函数的文档中看到一个巨大的“警告:此函数使用eval()”,但我宁愿依靠比这更安全的东西。例如(由于时间有限),我并非总是重读函数的文档。我也不认为我应该。有更快(即更有效)的方法来了解不安全的事物,例如墨菲在他的答案中链接的方法。
费米悖论

@Fermiparadox当您将eval排除在问题之外时,它将变得没有有用的示例。您现在是否在谈论由于故意疏忽而导致的不安全功能,例如未转义SQL参数。您是说测试代码对生产环境不安全?无论如何,现在我读到您对Amon答案的评论-基本上,您正在寻找如何保护自己的功能。
user1122069'3

Answers:


26

定义装饰“安全”输入的类型。在函数中f(),断言该参数具有此类型,否则将引发错误。足够聪明,永远不要定义快捷方式def g(x): return f(SafeInput(x))

例:

class SafeInput(object):
  def __init__(self, value):
    self._value = value

  def value(self):
    return self._value

def f(safe_string_to_eval):
  assert type(safe_string_to_eval) is SafeInput, "safe_string_to_eval must have type SafeInput, but was {}".format(type(safe_string_to_eval))
  ...
  eval(safe_string_to_eval.value())
  ...

a = f(SafeInput(local_file.some_str))

尽管可以很容易地避免这种情况,但使它更难于偶然地做到这一点。人们可能会批评这种严厉的类型检查,但他们会忽略这一点。由于SafeInput类型只是数据类型,而不是可以子类化的面向对象的类,因此使用来检查类型标识type(x) is SafeInput比使用来检查类型兼容性更安全isinstance(x, SafeInput)。由于我们确实要强制执行特定类型,因此在这里忽略显式类型并仅执行隐式鸭子类型也不令人满意。Python具有类型系统,因此让我们使用它来捕获可能的错误!


5
不过可能需要进行一些小的更改。assert自动删除,因为我将使用优化的python代码。if ... : raise NotSafeInputError需要一些类似的东西。
费米悖论

4
无论如何,如果您一直走这条路,我强烈建议您将eval()移至方法上SafeInput,这样就不需要显式的类型检查。给该方法指定其他任何类中未使用的名称。这样,您仍然可以出于测试目的对整个过程进行模拟。
凯文

@Kevin:eval似乎可以访问局部变量,因此代码可能取决于其对变量进行突变的方式。通过将eval移至另一种方法,它将不再具有突变局部变量的功能。
Sjoerd Job Postmus

进一步的步骤可能是让SafeInput构造函数获取本地文件的名称/句柄并进行读取,以确保数据确实来自本地文件。
Owen

1
我对python没有太多经验,但是抛出异常不是更好吗?在大多数语言中,可以关闭断言,并且(据我所知)断言是为了调试而不是出于安全性。异常并退出控制台可确保不会运行下一个代码。
user1122069'3

11

正如Joel曾经指出的那样,使错误的代码看起来错误(向下滚动至“真正的解决方案”部分,或者最好还是完整地阅读它;这是值得的,即使它解决了变量而不是函数名)。因此,我谦虚地建议将您的函数重命名为类似的名称f_unsafe_for_external_use()-您很可能会自己提出一些缩写方案。

编辑:我认为阿蒙的建议是一个很好的,基于OO的同一主题的变体:-)


0

作为@amon的建议的补充,您还可以考虑在此处组合策略模式以及方法对象模式。

class AbstractFoobarizer(object):
    def handle_cond(self, foo, bar):
        """
        Handle the event or something.
        """
        raise NotImplementedError

    def run(self):
        # do some magic
        pass

然后是“正常”实施

class PrintingFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        print("Something went very well regarding {} and {}".format(foo, bar))

以及admin-input-eval实现

class AdminControllableFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        # Look up code in database/config file/...
        code = get_code_from_settings()
        eval(code)

(注意:通过一个真实世界的例子,我也许可以给出一个更好的例子)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.