从不受信任的源序列化数据并安全地解包

7

我正在使用Pyramid作为回合制视频游戏数据传输的基础。客户端使用POST数据提交他们的操作,使用GET来检索序列化的游戏板数据。游戏数据有时可能包含字符串,但几乎总是两个整数和两个元组:

gamedata = (userid, gamenumber, (sourcex, sourcey), (destx, desty))

我的一般客户端框架是使用Pickle,转换为base64,使用urlencode并提交POST。然后服务器接收POST,解压缩单项字典,解码base64,然后反序列化数据对象。

我想使用Pickle,因为我可以使用类和值。将游戏数据作为POST字段提交只能给我字符串。

然而,Pickle被认为是不安全的。所以,我转向了pyYAML,它具有相同的目的。使用yaml.safe_load(data),我可以序列化数据而不会暴露安全漏洞。然而,safe_load非常安全,我甚至不能反序列化无害的元组或列表,即使它们只包含整数。

这里有什么中间地带吗?有没有一种方法可以序列化python结构,而不同时允许执行任意代码?

我的第一个想法是编写一个包装器来使用下划线在值名称中重新创建元组,例如,发送会将字典值source:(x,y)转换为source_0:x,source_1:y。我的第二个想法是这不是一种明智的开发方式。

编辑:这是我使用JSON的实现...它似乎不像YAML或Pickle那样强大,但我仍然担心可能存在安全漏洞。

客户端的构建在我进行实验时更加可见:

import urllib, json, base64

arbitrarydata = { 'id':14, 'gn':25, 'sourcecoord':(10,12), 'destcoord':(8,14)}

jsondata = json.dumps(arbitrarydata)
b64data = base64.urlsafe_b64encode(jsondata)
transmitstring = urllib.urlencode( [ ('data', b64data) ] )
urllib.urlopen('http://127.0.0.1:9000/post', transmitstring).read()

Pyramid服务器可以检索数据对象:

json.loads(base64.urlsafe_b64decode(request.POST['data'].encode('ascii')))

除此之外,我很想听听其他人对于在这种方法中使用POST数据的可接受性的看法。目前我的游戏客户端并不是基于浏览器的。


1
确实,您绝对不应该使用pickle来处理不受信任的数据。请参阅http://www.zopatista.com/plone/2007/11/09/one-cookie-please/。 - Martijn Pieters
@MartijnPieters,我理解那种栈语言的难以置信的有用性......在一百万个项目中只有一个。是否有pickle的某些子集不允许这些特定操作?似乎许多人在一个非常有用的系统中遇到了一个可怕缺陷,使他们非常头痛。 - John
不,没有。任意的Python对象意味着攻击者可以构造一些东西来允许访问。例如,在Python stdlib中有强大的基本服务器可用,当实例化时会打开端口。 - Martijn Pieters
3个回答

4

为什么不使用colander进行序列化和反序列化呢? Colander将对象模式转换为简单的数据结构,反之亦然,并且您可以使用JSON发送和接收此信息。

例如:

import colander

class Item(colander.MappingSchema):
    thing = colander.SchemaNode(colander.String(),
                                validator=colander.OneOf(['foo', 'bar']))
    flag = colander.SchemaNode(colander.Boolean())
    language = colander.SchemaNode(colander.String()
                                   validator=colander.OneOf(supported_languages)

class Items(colander.SequenceSchema):
    item = Item()

上面的设置定义了一个项目对象列表,但您也可以轻松定义特定于游戏的对象。
反序列化变成了:
    items = Items().deserialize(json.loads(jsondata))

而序列化是:

    json.dumps(Items().serialize(items))

除了让您往返传递Python对象外,它还验证序列化数据以确保其符合架构并未被篡改。

3

那么 json 呢?该库是Python标准库的一部分,它允许大多数通用数据进行序列化而不涉及任意代码执行。


0

我不认为裸的JSON提供了答案,因为我相信问题明确提到了pickling和值。我不相信使用直接的JSON可以序列化和反序列化Python类,而pickle可以。

我几乎在所有服务器之间的通信中都使用基于pickle的序列化方法,但始终包括非常严格的身份验证机制(例如RSA密钥对匹配)。然而,这意味着我只处理可信来源。

如果您绝对需要与不受信任的来源一起工作,我建议您至少尝试添加一个模式(就像@MartijnPieters建议的那样)来验证交易。我认为没有一种好的方法可以处理来自不受信任的来源的任意pickle数据。您必须做一些事情,例如用某个反汇编程序解析字节字符串,然后仅允许受信任的模式(或阻止不受信任的模式)。我不知道有什么可以做到这一点的pickle

但是,如果您的类足够简单……您可能可以使用JSONEncoder,它本质上将您的Python类转换为JSON可以序列化的内容……从而进行验证……

如何使一个类成为可 JSON 序列化的

然而,这意味着你必须从 JSONEncoder 派生出你的类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接