Python:我可以安全地解开不受信任的数据吗?

Nik*_*tio 9 python security pickle

泡菜模块文档右一开始说:

警告:pickle模块不能防止错误或恶意构造的数据.切勿取消从不受信任或未经身份验证的来源收到的数据.

然而,在限制全局变量的情况下,它似乎描述了一种使用允许对象的白名单来使unpickling数据安全的方法.

这是否意味着如果我使用RestrictedUnpickler仅允许某些"基本"类型的数据,或者是否存在此方法无法解决的其他安全问题,我是否可以安全地解开不受信任的数据?如果有,还有另一种方法可以使unpickling安全(显然代价是无法解开每个流)吗?

对于"基本类型",我的意思是:

  • bool
  • str,bytes,bytearray
  • int,float,complex
  • tuple,list,dict,setfrozenset

Ara*_*Fey 9

在这个答案中,我们将探讨 pickle 协议究竟允许攻击者做什么。这意味着我们将只依赖协议的记录功能,而不是实现细节(有一些例外)。换句话说,我们将假设pickle模块的源代码是正确的且没有错误,并且允许我们完全按照文档说明进行操作,仅此而已。

pickle 协议允许攻击者做什么?

Pickle允许类自定义它们的实例如何被pickle。在脱酸过程中,我们可以:

  • 调用(几乎)任何类的__setstate__方法(只要我们设法解开该类的实例)。
  • 借助该__reduce__方法,可以使用任意参数调用任意可调用对象(只要我们能够以某种方式访问​​可调用对象)。
  • 调用(几乎)任何拆封对象是appendextend__setitem__方法,再次感谢__reduce__
  • 访问Unpickler.find_class允许我们访问的任何属性。
  • 自由创建以下类型的实例:strbyteslisttupledictintfloatbool。这没有记录,但这些类型内置在协议本身中,不会通过Unpickler.find_class.

这里最有用的(从攻击者的角度来看)功能是调用可调用对象的能力。如果他们可以访问execeval,他们可以让我们执行任意代码。如果他们可以访问os.system或者subprocess.Popen他们可以运行任意 shell 命令。当然,我们可以拒绝他们访问这些Unpickler.find_class。但是我们究竟应该如何实现我们的find_class方法呢?哪些函数和类是安全的,哪些是危险的?

攻击者的工具箱

在这里,我将尝试解释攻击者可以用来做坏事的一些方法。让攻击者访问这些函数/类中的任何一个都意味着您处于危险之中。

  • unpickling期间任意代码执行:
    • execeval(废话)
    • os.system, os.popen,subprocess.Popen和所有其他subprocess函数
    • types.FunctionType,它允许从代码对象创建一个函数(可以用compile或来创建types.CodeType
    • typing.get_type_hints. 是的,你没看错。你问怎么样?好吧, typing.get_type_hints评估前向引用。因此,所有你需要的是一个对象__annotations__一样{'x': 'os.system("rm -rf /")'},并get_type_hints会运行代码为您服务。
    • functools.singledispatch. 我看到你难以置信地摇头,但这是真的。单调度函数有一个register方法,它在内部调用typing.get_type_hints.
    • ......可能还有更多
  • 无需经过即可访问事物Unpickler.find_class

    仅仅因为我们的find_class方法阻止攻击者直接访问某些东西并不意味着没有间接访问该东西的方法。

    请参阅 Ned Batchelder 的Eval,找出攻击者如何使用这些来访问几乎所有内容非常危险

  • 解压后的代码执行:

    攻击者不一定要unpickling 过程中做一些危险的事情——他们也可以尝试返回一个危险的对象,让意外地调用一个危险的函数。也许你调用typing.get_type_hints了 unpickled 的对象,或者你希望 unpickle aCuteBunny但实际上 unpickle aFerociousDragon并且在你尝试.pet()它时被咬掉了你的手。始终确保 unpickled 对象是您期望的类型,它的属性是您期望的类型,并且它没有您不期望的任何属性。

在这一点上,很明显没有多少模块/类/函数可以信任。当你实现你的find_class方法,从来没有写黑名单-总是写白名单,只包括事情你一定不能滥用。

那么问题的答案是什么?

如果你真的只允许访问boolstrbytesbytearrayintfloatcomplextuplelistdictsetfrozenset那么你很可能是安全的。但老实说 - 您可能应该改用 JSON。

总的来说,我认为大多数都是安全的——subprocess.Popen当然,除了像。攻击者可以做的最糟糕的事情是调用类 - 通常不应该做比返回该类的实例更危险的事情。

您真正需要注意的是允许访问函数(和其他非类可调用对象),以及如何处理 unpickled 对象。


Mar*_*ers 6

我甚至说没有安全的方法来使用pickle来处理不受信任的数据.

即使使用受限制的全局变量,Python的动态特性使得一个坚定的黑客仍然有机会找到回__builtins__映映射的方法,从那里到皇冠上的宝石.

请参见上规避限制斯内德尔德的博客文章eval()以同样的尺度适用pickle.

请记住,pickle它仍然是一种堆栈语言,你无法预见所有可能的对象,即使是一组有限的全局变量也允许任意调用.pickle文档也没有提到EXT*允许调用已copyreg安装扩展的操作码; 你必须在这里考虑安装在该注册表中的任何东西.所需要的只是一个向量,允许将对象调用转换为getattr等同于您的防御崩溃.

最起码用的加密签名数据,以便您可以验证的完整性.你将限制风险,但如果攻击者曾设法窃取你的签名秘密(密钥),那么他们可能会再次打败你的黑客泡菜.

我会使用像JSON这样的现有无害格式并添加类型注释; 例如,使用类型键将数据存储在字典中,并在加载数据时进行转换.

  • 为了使用 Ned 的过程从某个(白名单)对象获取“__builtins__”,您需要某种方法来检索对象属性(例如“getattr(obj, '__class__')”或“obj.__class__”)。我认为这对于 pickle 协议是不可能的。您可以检索全局变量(“GLOBAL”操作码),使用任意参数(“REDUCE”、“INST”、“OBJ”、“NEWOBJ”、“NEWOBJ_EX”)调用/实例化它们并*设置*它们的属性(“BUILD”) ),但您无法检索属性。我错过了什么吗? (2认同)
  • 我之前从未见过我的博客文章与pickle相关,但想知道它是否可以.Pickle使用基于堆栈的执行机器,但它不是Python字节码,因此可能性不同. (2认同)

enr*_*cis 2

在解决在标准库中添加安全替代方案的问题时,这个想法也在邮件列表python-ideas上进行了讨论。pickle例如这里

为了使其更安全,我将使用受限制的 unpickler 作为默认值(用于加载/加载),并强制人们在想要放松限制时覆盖它。明确地说,我会让 load/loads 仅适用于内置类型。

还有这里

我一直想要一个 pickle.loads() 版本,它接受允许实例化的类列表。

以下内容对您来说是否足够:http://docs.python.org/3.4/library/pickle.html#restricting-globals

它的确是。感谢您指出!我从未跳过文档的模块接口部分。也许页面顶部的警告也可以提到有一些方法可以减轻安全问题,并指出#restricting-globals?

是的,那是个好主意:-)

所以我不知道为什么文档没有改变,但根据我的说法,使用 aRestrictedUnpickler来限制可以 unpickle 的类型是一个安全的解决方案。当然,库中可能存在危害系统的错误,但 OpenSSL 中也可能存在向每个询问的人显示随机内存数据的错误。