Python 巨蟒泡菜-它是如何破碎的?
每个人都知道pickle不是存储用户数据的安全方式。盒子上甚至写着 我正在寻找在当前受支持的Python 巨蟒泡菜-它是如何破碎的?,python,serialization,escaping,pickle,Python,Serialization,Escaping,Pickle,每个人都知道pickle不是存储用户数据的安全方式。盒子上甚至写着 我正在寻找在当前受支持的cPython>=2.4版本中破坏pickle解析的字符串或数据结构的示例。有什么东西可以腌制,但不能不腌制?特定unicode字符是否存在问题?真的是大数据结构吗?很明显,旧的ASCII协议有一些问题,但是最新的二进制格式呢 我特别好奇pickle加载操作可能失败的方式,特别是当给定pickle本身生成的字符串时。pickle是否会在任何情况下继续解析 有什么样的边缘案例 编辑:以下是一些我正在寻找的例
cPython>=2.4
版本中破坏pickle解析的字符串或数据结构的示例。有什么东西可以腌制,但不能不腌制?特定unicode字符是否存在问题?真的是大数据结构吗?很明显,旧的ASCII协议有一些问题,但是最新的二进制格式呢
我特别好奇pickle加载
操作可能失败的方式,特别是当给定pickle本身生成的字符串时。pickle是否会在任何情况下继续解析
有什么样的边缘案例
编辑:以下是一些我正在寻找的例子:
- 在Python2.4中,可以对数组进行无错误的pickle,但不能取消对其进行pickle李>
- 在使用
设置实例变量之前,无法可靠地pickle从dict继承并调用\uuuuu setstate\uuuu
的对象。这可能是在酸洗Cookie对象时遇到的问题。看到和\uuuuu setitem\uuuuu
- Python2.4(和2.5?)将返回无穷大的pickle值(或接近该值的值,如1e100000),但可能(取决于平台)在加载时失败。看到和
- 最后一项很有趣,因为它揭示了一种情况,
标记实际上并没有停止解析——当标记作为文本的一部分存在时,或者更一般地说,当标记前面没有换行符时STOP
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)))