Python 巨蟒泡菜-它是如何破碎的?

Python 巨蟒泡菜-它是如何破碎的?,python,serialization,escaping,pickle,Python,Serialization,Escaping,Pickle,每个人都知道pickle不是存储用户数据的安全方式。盒子上甚至写着 我正在寻找在当前受支持的cPython>=2.4版本中破坏pickle解析的字符串或数据结构的示例。有什么东西可以腌制,但不能不腌制?特定unicode字符是否存在问题?真的是大数据结构吗?很明显,旧的ASCII协议有一些问题,但是最新的二进制格式呢 我特别好奇pickle加载操作可能失败的方式,特别是当给定pickle本身生成的字符串时。pickle是否会在任何情况下继续解析 有什么样的边缘案例 编辑:以下是一些我正在寻找的例

每个人都知道pickle不是存储用户数据的安全方式。盒子上甚至写着

我正在寻找在当前受支持的
cPython>=2.4
版本中破坏pickle解析的字符串或数据结构的示例。有什么东西可以腌制,但不能不腌制?特定unicode字符是否存在问题?真的是大数据结构吗?很明显,旧的ASCII协议有一些问题,但是最新的二进制格式呢

我特别好奇pickle
加载
操作可能失败的方式,特别是当给定pickle本身生成的字符串时。pickle是否会在任何情况下继续解析

有什么样的边缘案例

编辑:以下是一些我正在寻找的例子:

  • 在Python2.4中,可以对数组进行无错误的pickle,但不能取消对其进行pickle
  • 在使用
    \uuuuu setstate\uuuu
    设置实例变量之前,无法可靠地pickle从dict继承并调用
    \uuuuu setitem\uuuuu
    的对象。这可能是在酸洗Cookie对象时遇到的问题。看到和
  • Python2.4(和2.5?)将返回无穷大的pickle值(或接近该值的值,如1e100000),但可能(取决于平台)在加载时失败。看到和
  • 最后一项很有趣,因为它揭示了一种情况,
    STOP
    标记实际上并没有停止解析——当标记作为文本的一部分存在时,或者更一般地说,当标记前面没有换行符时

可以pickle类实例。如果我知道您的应用程序使用什么类,那么我可以破坏它们。一个人为的例子:

import subprocess

class Command(object):
    def __init__(self, command):
        self._command = self._sanitize(command)

    @staticmethod
    def _sanitize(command):
        return filter(lambda c: c in string.letters, command)

    def run(self):
        subprocess.call('/usr/lib/myprog/%s' % self._command, shell=True)
现在,如果您的程序创建
Command
实例并使用pickle保存它们,并且我可以破坏或注入该存储,那么我可以通过设置
self.\u Command
直接运行我选择的任何命令

实际上,无论如何,我的示例都不应该被认为是安全代码。但是请注意,如果
sanitize
函数是安全的,那么整个类也是安全的,除了可能使用pickle从不受信任的数据破坏这一点之外。因此,有一些程序是安全的,但不适当地使用pickle会使程序变得不安全


危险在于,使用pickle的代码可能会按照相同的原则被破坏,但在外观无辜的代码中,漏洞远没有那么明显。最好的做法是始终避免使用pickle加载不受信任的数据。

这是一个大大简化的示例,说明pickle不喜欢我的数据结构

import cPickle as pickle

class Member(object):
    def __init__(self, key):
        self.key = key
        self.pool = None
    def __hash__(self):
        return self.key

class Pool(object):
    def __init__(self):
        self.members = set()
    def add_member(self, member):
        self.members.add(member)
        member.pool = self

member = Member(1)
pool = Pool()
pool.add_member(member)

with open("test.pkl", "w") as f:
    pickle.dump(member, f, pickle.HIGHEST_PROTOCOL)

with open("test.pkl", "r") as f:
    x = pickle.load(f)
众所周知,Pickle对于循环结构有点滑稽,但如果将自定义哈希函数和set/dict混合在一起,那么事情就会变得相当棘手

在这个特定的示例中,它会部分取消勾选成员,然后遇到池。因此,它会部分取消勾选池并遇到成员集。因此,它创建集合并尝试将部分未勾选的成员添加到集合中。在这一点上,它将在自定义哈希函数中消失,因为该成员仅部分取消勾选。我不敢想象如果哈希函数中有一个“if hasattr…”会发生什么

$ python --version
Python 2.6.5
$ python test.py
Traceback (most recent call last):
  File "test.py", line 25, in <module>
    x = pickle.load(f)
  File "test.py", line 8, in __hash__
    return self.key
AttributeError: ("'Member' object has no attribute 'key'", <type 'set'>, ([<__main__.Member object at 0xb76cdaac>],))
$python--版本
Python 2.6.5
$python test.py
回溯(最近一次呼叫最后一次):
文件“test.py”,第25行,在
x=酸洗负荷(f)
文件“test.py”,第8行,在散列中__
返回自密钥
AttributeError:(“'Member'对象没有属性'key'”,([]))

如果您对
pickle
(或
cPickle
)的失败感兴趣,因为它只是一个稍微不同的导入,那么您可以使用python中所有不同对象类型的这个不断增长的列表来相当轻松地进行测试

程序包dill包含一些函数,可以发现对象如何无法pickle,例如通过捕获它抛出的错误并将其返回给用户

dill.dill
具有这些功能,您也可以为
pickle
cPickle
构建这些功能,只需剪切粘贴和
import pickle
import cPickle as pickle
(或
import dill as pickle
):

并将其包含在
dill.detect
中:

def baditems(obj, exact=False, safe=False): #XXX: obj=globals() ?
    """get items in object that fail to pickle"""
    if not hasattr(obj,'__iter__'): # is not iterable
        return [j for j in (badobjects(obj,0,exact,safe),) if j is not None]
    obj = obj.values() if getattr(obj,'values',None) else obj
    _obj = [] # can't use a set, as items may be unhashable
    [_obj.append(badobjects(i,0,exact,safe)) for i in obj if i not in _obj]
    return [j for j in _obj if j is not None]


def badobjects(obj, depth=0, exact=False, safe=False):
    """get objects that fail to pickle"""
    if not depth:
        if pickles(obj,exact,safe): return None
        return obj
    return dict(((attr, badobjects(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))

def badtypes(obj, depth=0, exact=False, safe=False):
    """get types for objects that fail to pickle"""
    if not depth:
        if pickles(obj,exact,safe): return None
        return type(obj)
    return dict(((attr, badtypes(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))
最后一个函数,可以用来测试
dill中的对象。\u objects

def errors(obj, depth=0, exact=False, safe=False):
    """get errors for objects that fail to pickle"""
    if not depth:
        try:
            pik = copy(obj)
            if exact:
                assert pik == obj, \
                    "Unpickling produces %s instead of %s" % (pik,obj)
            assert type(pik) == type(obj), \
                "Unpickling produces %s instead of %s" % (type(pik),type(obj))
            return None
        except Exception:
            import sys
            return sys.exc_info()[1]
    return dict(((attr, errors(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))

您可能想阅读的安全部分。Space_C0wb0y:是的,这就是我在问题的第一行提到它的原因。我想我明白您的意思,但如果我们允许用户随意修改或注入pickle,安全问题就直接得多:他们可以在pickle加载时执行任何代码。例如:
pickle.load(“cos\nsystem\n(S'ls~”\ntR.)
,它执行对
os.system()的任意调用
。我正在寻找不涉及pickle这一特定方面的问题。太棒了!这是一个非常有趣的现实世界示例,正好说明了我正在寻找的这类问题。循环数据结构绝对是一种很好的解决问题的方法。
def errors(obj, depth=0, exact=False, safe=False):
    """get errors for objects that fail to pickle"""
    if not depth:
        try:
            pik = copy(obj)
            if exact:
                assert pik == obj, \
                    "Unpickling produces %s instead of %s" % (pik,obj)
            assert type(pik) == type(obj), \
                "Unpickling produces %s instead of %s" % (type(pik),type(obj))
            return None
        except Exception:
            import sys
            return sys.exc_info()[1]
    return dict(((attr, errors(getattr(obj,attr),depth-1,exact,safe)) \
           for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe)))