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

时间:2014-08-17 21:22:53

标签: python security pickle

pickle module documentation在开头说:

  

警告:   泡菜模块不是为了防止错误或安全   恶意构造的数据。切勿取消从中收到的数据   不受信任或未经认证的来源。

然而,在restricting globals下面,它似乎描述了一种使用允许对象的白名单来确保使用unickling数据的方法。

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

使用"基本类型"我的意思是:

  • bool
  • strbytesbytearray
  • intfloatcomplex
  • tuplelistdictsetfrozenset

3 个答案:

答案 0 :(得分:5)

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

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

Ned Batchelder's blog posts on circumventing restrictions on eval()同等适用于pickle

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

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

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

答案 1 :(得分:2)

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

  

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

还有here

  

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

     
    

以下内容是否适合您:http://docs.python.org/3.4/library/pickle.html#restricting-globals

         
      

确实如此。谢谢你指出来!我从来没有超过文档的模块界面部分。也许页面顶部的警告也可以提到有减轻安全问题的方法,并指出#restrictionting-globals?

             
        

是的,这是一个好主意: - )

      
    
  

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

答案 2 :(得分:2)

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

泡菜协议允许攻击者做什么?

腌制allows classes to customize how their instances are pickled。在去皮过程中,我们可以:

  • (几乎)调用任何类的__setstate__方法(只要我们设法释放该类的实例)。
  • 借助__reduce__方法(只要我们能够以某种方式获得对可调用对象的访问权),即可使用任意参数调用任意可调用对象。
  • 再次(借助)__reduce__来调用(几乎)任何未腌制对象的appendextend__setitem__方法。
  • 访问Unpickler.find_class允许我们使用的任何属性。
  • 自由创建以下类型的实例:strbyteslisttupledictint,{{1 }},float。这没有记录,但是这些类型内置在协议本身中,不会通过bool

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

攻击者的工具箱

在这里,我将尝试解释攻击者可以用来做恶事的一些方法。使攻击者可以使用任何这些功能/类,意味着您有危险。

  • 解开过程中的任意代码执行:
    • execeval(杜)
    • os.systemos.popensubprocess.Popen和所有其他find_class函数
    • types.FunctionType,它允许从代码对象创建函数(可以使用compiletypes.CodeType创建)
    • typing.get_type_hints。是的,你看的没错。你怎么问?好吧,subprocess会评估前向引用。因此,您所需要的只是一个typing.get_type_hints对象,例如__annotations__,而{'x': 'os.system("rm -rf /")'}将为您运行代码。
    • functools.singledispatch。我看到你难以置信地摇了摇头,但这是真的。单调度功能具有get_type_hints方法,该方法在内部调用register
    • ...还有可能更多
  • 无需通过typing.get_type_hints 即可访问内容:

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

    请参阅Ned Batchelder的Eval is really dangerous,以了解攻击者如何使用它们来获取几乎所有内容的访问权限。

  • 解开代码后的代码执行

    攻击者不一定要在拔除过程中 做一些危险的事情-他们还可以尝试返回危险的对象并让 you 调用危险的函数事故。也许您在未腌制的物体上呼叫dict.__getitem__,或者您可能想解开typing.get_type_hints,但想解开CuteBunny并在尝试FerociousDragon时将其咬断。始终确保未腌制的对象属于您期望的类型,其属性属于您期望的类型,并且不具有您不期望的任何属性。

在这一点上,显然可以信任的模块/类/功能并不多。当您实现.pet()方法时,永远不要永远写黑名单-总是写白名单,并且只包含您确定不会被滥用的内容。

那问题的答案是什么?

如果您真的仅允许访问find_classboolstrbytesbytearray,{{1 }},intfloatcomplextuplelistdict,那么您很有可能安全。但说实话-您可能应该改用JSON。

通常来说,我认为大多数是安全的-当然,除了set这样的例外。攻击者可以做的最坏的事情就是调用类-通常,该类不应做比返回该类的实例更危险的事情。

您真正需要注意的是允许访问功能(以及其他非类可调用对象)以及如何处理未腌制的对象。